Compare commits

...

177 Commits

Author SHA1 Message Date
dependabot[bot]
55b2e30cec Update markupsafe requirement from ==2.1.* to ==3.0.* in /docker/main
Updates the requirements on [markupsafe](https://github.com/pallets/markupsafe) to permit the latest version.
- [Release notes](https://github.com/pallets/markupsafe/releases)
- [Changelog](https://github.com/pallets/markupsafe/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/markupsafe/compare/2.1.0...3.0.0)

---
updated-dependencies:
- dependency-name: markupsafe
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-08 11:30:16 +00:00
Josh Hawkins
74efc94649 Always display confidence chip on similarity searches (#14207) 2024-10-07 20:15:49 -06:00
Nicolas Mowen
2541a345d0 Improve Nvidia GPU stats (#14206)
* :Add support for nvidia driver info

* Don't show temperature if detector isn't called coral

* Add encoder and decoder info for Nvidia GPUs

* Fix device info

* Implement GPU info for nvidia GPU

* Update web/src/views/system/GeneralMetrics.tsx

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

* Update web/src/views/system/GeneralMetrics.tsx

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

---------

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
2024-10-07 20:15:31 -06:00
Josh Hawkins
23ce1e930d Adjust zscore bias for descriptions (#14205) 2024-10-07 18:51:37 -06:00
Josh Hawkins
6ebad84160 initialize path before calling super() (#14203) 2024-10-07 16:17:57 -05:00
Josh Hawkins
24ac9f3e5a Use sqlite-vec extension instead of chromadb for embeddings (#14163)
* swap sqlite_vec for chroma in requirements

* load sqlite_vec in embeddings manager

* remove chroma and revamp Embeddings class for sqlite_vec

* manual minilm onnx inference

* remove chroma in clip model

* migrate api from chroma to sqlite_vec

* migrate event cleanup from chroma to sqlite_vec

* migrate embedding maintainer from chroma to sqlite_vec

* genai description for sqlite_vec

* load sqlite_vec in main thread db

* extend the SqliteQueueDatabase class and use peewee db.execute_sql

* search with Event type for similarity

* fix similarity search

* install and add comment about transformers

* fix normalization

* add id filter

* clean up

* clean up

* fully remove chroma and add transformers env var

* readd uvicorn for fastapi

* readd tokenizer parallelism env var

* remove chroma from docs

* remove chroma from UI

* try removing custom pysqlite3 build

* hard code limit

* optimize queries

* revert explore query

* fix query

* keep building pysqlite3

* single pass fetch and process

* remove unnecessary re-embed

* update deps

* move SqliteVecQueueDatabase to db directory

* make search thumbnail take up full size of results box

* improve typing

* improve model downloading and add status screen

* daemon downloading thread

* catch case when semantic search is disabled

* fix typing

* build sqlite_vec from source

* resolve conflict

* file permissions

* try build deps

* remove sources

* sources

* fix thread start

* include git in build

* reorder embeddings after detectors are started

* build with sqlite amalgamation

* non-platform specific

* use wget instead of curl

* remove unzip -d

* remove sqlite_vec from requirements and load the compiled version

* fix build

* avoid race in db connection

* add scale_factor and bias to description zscore normalization
2024-10-07 14:30:45 -06:00
Rui Alves
757150dec1 Use Swagger documentation for Frigate HTTP API (#14178)
* Updated documentation

* docusaurus.config and sidebars converted to Typescript to allow for typings

* Added type for sidebars.ts

* Replaced integrations/api.md with automatically generated openAPI specification. Make sidebar collapsible to increase readability

* Fix HTTP API links in the documentation

* Added rust as language in the openapi sidebar

* Make sure configuration/pwa is present

* Fix API slug

* Fix links

* Revert sidebarCollapsible configuration

* Make HTTP API sidebar collapsed by default. Added CSS for OpenAPI methods

* Proper localhost server path

* Proper localhost server path

* No introduction page

* Lint
2024-10-07 14:27:35 -06:00
Josh Hawkins
ddcec82b61 Explicitly set video tag dimensions to fit inside dialog (#14184) 2024-10-07 07:19:22 -06:00
Nicolas Mowen
74047453ef Bug Fixes (#14193)
* fix getting device from empty list

* Overwrite all fields

* Fix header too low
2024-10-07 07:18:09 -06:00
Rémi Bédard-Couture
dcaed0e90f Log correct tensorRT version when debugging (#14182) 2024-10-06 15:33:03 -06:00
Josh Hawkins
cae304e07f Revamp object snapshot tab (#14180)
* Revamp object snapshot tab

* Make snapshots and thumbnails left justified in the review pane
2024-10-06 11:43:36 -06:00
Blake Blackshear
47ad0ca993 Merge remote-tracking branch 'origin/master' into dev 2024-10-05 10:48:14 -05:00
Nicolas Mowen
9c751230a1 Include lsof in container (#14145) 2024-10-03 17:23:37 -05:00
gtsiam
a468ed316d Added stop_event to util.Process (#14142)
* Added stop_event to util.Process

util.Process will take care of receiving signals when the stop_event is
accessed in the subclass. If it never is, SystemExit is raised instead.

This has the effect of still behaving like multiprocessing.Process when
stop_event is not accessed, while still allowing subclasses to not deal
with the hassle of setting it up.

* Give each util.Process their own logger

This will help to reduce boilerplate in subclasses.

* Give explicit types to util.Process.__init__

This gives better type hinting in the editor.

* Use util.Process facilities in AudioProcessor

Boilerplate begone!

* Removed pointless check in util.Process

The log_listener.queue should never be None, unless something has gone
extremely wrong in the log setup code. If we're that far gone, crashing
is better.

* Make sure faulthandler is enabled in all processes

This has no effect currently since we're using the fork start_method.
However, when we inevidably switch to forkserver (either by choice, or
by upgrading to python 3.14+) not having this makes for some really fun
failure modes :D
2024-10-03 11:03:43 -06:00
Nicolas Mowen
e725730982 Fix shared memory frames being stuck when a camera capture crashed (#14140)
* Fix shared memory frames being stuck when a camera capture crashed

* Update ffmpeg build
2024-10-03 09:31:07 -06:00
Nicolas Mowen
21c12d118b Correct preview docs (#14136) 2024-10-03 08:40:49 -05:00
Josh Hawkins
b9e74ee9ab Fix autotracking absolute zoom interpolation logic (#14139) 2024-10-03 08:40:27 -05:00
Nicolas Mowen
0f2cff5078 Fix mjpeg api (#14138) 2024-10-03 07:33:06 -06:00
gtsiam
e5e196bd7f Fix logging (#14122)
Fixes logging without introducing more junk into FrigateApp.
2024-10-03 06:33:53 -06:00
Nicolas Mowen
077402406b Make env vars warning more clear (#14128) 2024-10-02 21:52:58 -05:00
gtsiam
54900ae318 Properly call super() in subclasses (#14124) 2024-10-02 19:35:46 -06:00
gtsiam
3c015bf822 Do not migrate db to new config path (#14123)
I just saw this, and I would be very surprised by that behaviour as a
user. Changing the db path would randomly move the database, and
changing it back (or to anything, really) would not. These kinds of
advanced settings are generally expected to do one thing: Change the
path frigate opens the database from. The end.
2024-10-02 19:34:14 -06:00
Josh Hawkins
a1efcfb2d0 Fix alert thumbnails on iOS and safari desktop (#14121) 2024-10-02 13:49:52 -06:00
Nicolas Mowen
75d531285a Don't consider segments containing unprocessed frames in segment count (#14120) 2024-10-02 12:55:55 -05:00
Nicolas Mowen
0aad7db2d2 UI tweaks (#14118)
* Reset snapshot state when event is moved

* Add page listener for review page

* Add same listener for search page

* Use content height
2024-10-02 09:32:12 -05:00
Nicolas Mowen
20c3b890ae Clean up hwaccel docs (#14117) 2024-10-02 08:58:53 -05:00
Josh Hawkins
0126960d79 UI improvements and fixes (#14116)
* Add slider to adjust number of columns in search view

* Work around safari bug with alert thumbnails

* adjust gap
2024-10-02 07:59:53 -05:00
Josh Hawkins
849d441c5c Azure genai fixes (#14103)
* Azure fixes

* clarify docs

* sublabels fix
2024-10-01 16:05:16 -06:00
Josh Hawkins
b5f5627ca6 Add Azure OpenAI as genai provider (#14102)
* add azure openai genai client

* docs
2024-10-01 13:57:40 -06:00
Nicolas Mowen
5b0c1e5b9e Fix event entries (#14093) 2024-10-01 11:07:13 -05:00
Nicolas Mowen
3cff0df0ce Bug fixes (#14092)
* Fix path of selected GPU

* Fix selection bug

* Simplify
2024-10-01 07:54:27 -06:00
Nicolas Mowen
15fa55c223 Refactor attribute saving (#14090)
* Refactor attribute saving

* Ensure sub label is not overwritten

* Formatting

* Fix unused
2024-10-01 07:31:03 -06:00
Nicolas Mowen
594ca3a04b Don't assume single render device is D128 (#14091) 2024-10-01 07:27:41 -06:00
Josh Hawkins
fafe5623d1 UI tweaks (#14088)
* fix squashed alert thumbnails in filmstrip

* add genai debug logs

* consistent themed image loading indicator background color

* improve image loading skeleton in object lifecycle pane

* less rounding when screen is smaller

* use browser back button to dismiss review pane

* initial state
2024-10-01 07:01:45 -06:00
dependabot[bot]
141cf39368 Bump docker/login-action from 3.2.0 to 3.3.0 (#12555)
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.2.0 to 3.3.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](0d4c9c5ea7...9780b0c442)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-01 06:03:55 -06:00
Josh Hawkins
1fa050fd7a Add genai use_snapshot to reference config (#14078) 2024-09-30 17:46:09 -05:00
Nicolas Mowen
f36e7430ae Fix logging (#14079)
* Fix log level setting

* fix app.py mypy issues
2024-09-30 17:45:22 -05:00
Nicolas Mowen
94fd75e014 Misc Improvements (#14076)
* Return ID of export in http response

* Ignore keyboard listener when typing in text field

* Add other keyboard listeners
2024-09-30 16:55:44 -05:00
Josh Hawkins
95d6da3111 Add ability to configure genai to use snapshot instead of thumbnails (#14077)
* Allow embedding of snapshot for description via config option

* docs

* frontend button

* Backend

* crop snapshot to region

* only show dropdown when event has snapshot

* fix cursor on dropdown

* crop on initial generation as well

* use enum for type

* fix type
2024-09-30 15:54:53 -06:00
Nicolas Mowen
4dc4704bb4 Improve Intel GPU stats (#14073)
* Fix formatting

* Fix test
2024-09-30 16:41:14 -05:00
Nicolas Mowen
18bf7f93fa Improve rocm handling of different models (#14072)
* Improve rocm handling of different models

* Formatting

* Fix type check
2024-09-30 16:40:46 -05:00
Nicolas Mowen
c73f694c63 Improve gamma filter (#14065)
* Improve gamma filter

* Fix tests
2024-09-30 11:42:51 -06:00
Josh Hawkins
3688a3bc67 Misc fixes (#14064)
* Add loading indicator when explore view is revalidating

* Portal tooltip in object lifecycle pane

* Better config file handling

* Only manually set aspect ratio when using alert videos
2024-09-30 07:32:54 -06:00
Stepan Legachev
775a3a1c22 docs: fix argument spelling (#13366) 2024-09-28 18:17:32 -06:00
gtsiam
bbbb3b4a06 Split config.py into multiple files (#14038)
* Replace logging.warn with logging.warning

* Install config global state early

* Split config.py into more manageable pieces
2024-09-28 14:21:42 -05:00
Nicolas Mowen
576191cd4e Remove debug log (#14037) 2024-09-28 10:26:11 -05:00
Nicolas Mowen
38d398c967 Dynamic attributes config (#14035)
* Add config for attribute map and generate all labels from the map

* Update docs

* Formatting

* Use the dynamic label map

* Fix check

* Fix docs typo
2024-09-28 07:49:04 -06:00
Josh Hawkins
7da44115d3 Clarify storage metrics values with a popover (#14034) 2024-09-28 08:24:14 -05:00
Nokius
b54032bdc7 make the hailo user instalation script a bit more failsafe (#13992)
This will checks if the firmware folder is present if not it will created it informs the user to reboot to load the firmware for the hailo8l detector.
2024-09-28 05:55:17 -06:00
Nicolas Mowen
cab497e81e Fix dynamic api ordering (#14022)
* Fix dynamic api ordering

* Formatting
2024-09-27 12:09:53 -06:00
Nicolas Mowen
50e9c67609 Update Templates (#14019)
* Update general support template

* Update camera support

* Update config-support.yml

* Update detector support

* Update general-support.yml

* Update hardware-acceleration-support.yml

* Create pull_request_template.md
2024-09-27 10:48:51 -05:00
Josh Hawkins
bd57ea0110 Revert all cameras switches behavior (#14015) 2024-09-27 08:13:15 -06:00
Nicolas Mowen
05fe7f8a48 Fixes & Tweaks (#14013)
* Rework to create util for onnx initialization

* Fix shm log

* Fix onClick exceptoins
2024-09-27 07:41:48 -06:00
gtsiam
c0bd3b362c Custom classes for Process and Metrics (#13950)
* Subclass Process for audio_process

* Introduce custom mp.Process subclass

In preparation to switch the multiprocessing startup method away from
"fork", we cannot rely on os.fork cloning the log state at fork time.
Instead, we have to set up logging before we run the business logic of
each process.

* Make camera_metrics into a class

* Make ptz_metrics into a class

* Fixed PtzMotionEstimator.ptz_metrics type annotation

* Removed pointless variables

* Do not start audio processor when no audio cameras are configured
2024-09-27 07:53:23 -05:00
Josh Hawkins
6381028fd6 Ensure config file naming is consistent (#14011) 2024-09-27 07:52:42 -05:00
Josh Hawkins
1f328be1bd UI fixes (#14010)
* Fix export time picker on iOS

* Fix dialog on iPad
2024-09-27 07:52:31 -05:00
Nicolas Mowen
ddfdb71783 Fix snapshot access (#13996) 2024-09-26 20:05:48 -05:00
Nicolas Mowen
da1478c0c1 Update object_detectors.md (#13994)
* Update object_detectors.md

* Use info

* Move CPU detector to bottom

* Moce CPU to bottom

* Add missing detector keys
2024-09-26 18:58:58 -05:00
Josh Hawkins
40fe3b4358 Search functionality and UI tweaks (#13978)
* Portal tooltips

* Add ability to time_range filter chroma searches

* centering and padding consistency

* add event id back to chroma metadata

* query sqlite first and pass those ids to chroma for embeddings search

* ensure we pass timezone to the api call

* remove object lifecycle from search details for non-object events

* simplify hour calculation

* fix query without filters

* bump chroma version

* chroma 0.5.7

* fix selecting camera group in cameras filter button
2024-09-26 15:30:56 -05:00
Nicolas Mowen
20fd1db0f4 Update hailo driver to 4.18 (#13991) 2024-09-26 12:16:08 -06:00
Nicolas Mowen
a65aaab849 Fixes (#13990)
* Fix ROCm input name

* Fix incorrect parsing of None
2024-09-26 12:42:21 -05:00
Nicolas Mowen
a5595189ed Fix ROCm inference (#13988) 2024-09-26 12:16:26 -05:00
Nicolas Mowen
4a1da3ebc5 Fix saving config (#13985) 2024-09-26 11:19:37 -05:00
Nicolas Mowen
35a4460334 Tweaks (#13983)
* Add onConnect to mqtt and doucment

* Add play pause via mouse click
2024-09-26 09:12:15 -05:00
Nicolas Mowen
a6ccb37683 Fix config saving api (#13969) 2024-09-25 17:22:11 -05:00
Josh Hawkins
68465aed49 Increase healthcheck start period to 5 minutes (#13968) 2024-09-25 15:14:10 -06:00
Nicolas Mowen
fc3aac96f2 Remove incorrect (#13966) 2024-09-25 14:53:17 -05:00
Josh Hawkins
32c7669b28 Search UI tweaks (#13965)
* Prevent keyboard shortcuts from running when input is focused

* fix reset button and update time pickers when using input

* simplify css

* consistent button order and spacing
2024-09-25 12:45:42 -06:00
Nicolas Mowen
fef30bc671 Peroparly handle text based responses (#13964) 2024-09-25 13:35:30 -05:00
Josh Hawkins
ae547d27e4 Use not instead of is None (#13962) 2024-09-25 11:53:25 -06:00
Josh Hawkins
be3e1831d4 Add ability to use 12 hour time in input time filter (#13961) 2024-09-25 12:15:08 -05:00
Nicolas Mowen
45aceea53b Return version as html (#13960) 2024-09-25 10:06:17 -06:00
Nicolas Mowen
25819584bd Add ability to filter search by time range (#13946)
* Add ability to filter by time range

* Cleanup

* Handle input with tags

* fix input for time_range filter

* fix before and after filters

* clean up

* Ensure the default value works as expected

* Handle time range in am/pm based on browser

* Fix arrow

* Fix text

* Handle midnight case

* fix width

* Fix bg

* Fix bg

* Fix mobile spacing

* y spacing

* remove left padding

---------

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
2024-09-25 10:05:40 -06:00
Nicolas Mowen
4c24b70d47 Do not use a default value of datetime for params (#13957)
* Do not use a default value of datetime for params

* Formatting

* Remove unused

* Undo
2024-09-25 10:49:54 -05:00
Josh Hawkins
4c12673fbb Add ability to restrict genai to objects and zones at the camera level (#13958)
* Add ability to restrict genai to labels and zones at the camera level

* fix comment

* clarify docs

* use objects instead of labels

* docs

* object list
2024-09-25 09:42:39 -06:00
Nicolas Mowen
e935db5075 Api fixes (#13955)
* Fix false positive submission

* Make sure None is not sent as rate limit
2024-09-25 08:01:08 -05:00
Josh Hawkins
a7f1f8d327 Add 1s timeout to metadata subscriber in embeddings maintainer (#13947) 2024-09-24 19:08:12 -05:00
Josh Hawkins
8c540d7210 Add error logging to genai clients (#13943) 2024-09-24 14:04:35 -06:00
Nicolas Mowen
4c4b884f8e Review api fixes (#13942)
* Fix order of review api

* remove uneccesary params
2024-09-24 14:32:50 -05:00
Josh Hawkins
a3d3fe07ce PTZ camera support docs update (#13941)
* Add user reports for ptz cameras/autotracking

* remove message
2024-09-24 12:41:58 -06:00
Josh Hawkins
1ae521f560 regenerate description fixes (#13940) 2024-09-24 12:34:29 -06:00
Nicolas Mowen
7854e1c2c1 Fix plus api (#13937)
* Fix plus api

* Fix regenerate api
2024-09-24 12:22:11 -05:00
Nicolas Mowen
a9ff795948 More api fixes (#13936)
* Set correect return type for raw config

* Simplify parsing

* Formatting
2024-09-24 09:54:03 -06:00
Nicolas Mowen
a8e2f97260 Improve performance of image fetching (#13933) 2024-09-24 09:50:20 -05:00
Nicolas Mowen
d17253b023 Fix apis (#13932) 2024-09-24 09:27:10 -05:00
Josh Hawkins
ecbf0410eb Regenerate genai tracked object descriptions (#13930)
* add genai to frigateconfig

* add regenerate button if genai is enabled

* add endpoint and new zmq pub/sub model

* move publisher to app

* dont override

* logging

* debug timeouts

* clean up

* clean up

* allow saving of empty description

* ensure descriptions can be empty

* update search detail when results change

* revalidate explore page on focus

* global mutate hook

* description websocket hook and dispatcher

* revalidation and mutation

* fix merge conflicts

* update tests

* fix merge conflicts

* fix response message

* fix response message

* fix fastapi

* fix test

* remove log

* json content

* fix content response

* more json content fixes

* another one
2024-09-24 08:14:51 -06:00
Rui Alves
cffc431bf0 Frigate HTTP API using FastAPI (#13871)
* POC: Added FastAPI with one endpoint (get /logs/service)

* POC: Revert error_log

* POC: Converted preview related endpoints to FastAPI

* POC: Converted two more endpoints to FastAPI

* POC: lint

* Convert all media endpoints to FastAPI. Added /media prefix (/media/camera && media/events && /media/preview)

* Convert all notifications API endpoints to FastAPI

* Convert first review API endpoints to FastAPI

* Convert remaining review API endpoints to FastAPI

* Convert export endpoints to FastAPI

* Fix path parameters

* Convert events endpoints to FastAPI

* Use body for multiple events endpoints

* Use body for multiple events endpoints (create and end event)

* Convert app endpoints to FastAPI

* Convert app endpoints to FastAPI

* Convert auth endpoints to FastAPI

* Removed flask app in favour of FastAPI app. Implemented FastAPI middleware to check CSRF, connect and disconnect from DB. Added middleware x-forwared-for headers

* Added starlette plugin to expose custom headers

* Use slowapi as the limiter

* Use query parameters for the frame latest endpoint

* Use query parameters for the media snapshot.jpg endpoint

* Use query parameters for the media MJPEG feed endpoint

* Revert initial nginx.conf change

* Added missing even_id for /events/search endpoint

* Removed left over comment

* Use FastAPI TestClient

* severity query parameter should be a string

* Use the same pattern for all tests

* Fix endpoint

* Revert media routers to old names. Order routes to make sure the dynamic ones from media.py are only used whenever there's no match on auth/etc

* Reverted paths for media on tsx files

* Deleted file

* Fix test_http to use TestClient

* Formatting

* Bind timeline to DB

* Fix http tests

* Replace filename with pathvalidate

* Fix latest.ext handling and disable uvicorn access logs

* Add cosntraints to api provided values

* Formatting

* Remove unused

* Remove unused

* Get rate limiter working

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2024-09-24 07:05:30 -06:00
gtsiam
dc54981784 Move more things out of FrigateApp (#13897)
* Moved FrigateApp.init_config() into FrigateConfig.load()

* Move frigate config loading into main

* Store PlusApi in FrigateConfig

* Register SIGTERM handler in main

* Ensure logging is setup during config parsing

* Removed pointless try

* Moved config initialization out of FrigateApp

* Made FrigateApp.shm_frame_count into a function

* Removed log calls from signal handlers

python's logging calls are not re-entrant, which caused at least one of
these to deadlock randomly.

* Reopen stdout/err on process fork

This helps avoid deadlocks (https://github.com/python/cpython/issues/91776).

* Make mypy happy

* Whoops. I might have forgotten to save.

Truly an amateur mistake.

* Always call FrigateApp.stop()
2024-09-24 07:07:47 -05:00
Josh Hawkins
a7ed90f042 try deleting zoom spaces if disabled (#13915) 2024-09-23 15:34:08 -05:00
Nicolas Mowen
08941ab39a Improve hailo docs (#13910)
* Fix command for hailo8 install

* Update object_detectors.md
2024-09-23 09:53:54 -05:00
ofcedwards
b81a8d26e4 Disable keepalive for Safari in nginx vod location (#13909) 2024-09-23 08:02:50 -06:00
Nicolas Mowen
af84af7b7a Don't print error for requesting camera info (#13905) 2024-09-23 08:49:34 -05:00
Josh Hawkins
0f813962be Chroma fixes (#13902)
* Ensure descriptions saved in chroma are non-empty

* delete only existing ids in event cleanup

* add debug logging
2024-09-23 06:53:19 -06:00
Josh Hawkins
fe57f7f489 Search UI tweaks (#13903) 2024-09-23 06:39:20 -06:00
Sean Kelly
12e2c04486 Decrease number of X ticks on system and camera graphs (#13891) 2024-09-22 15:57:19 -06:00
Nicolas Mowen
6bafb68d77 Remove PyYAML usages and dependency (#13889) 2024-09-22 14:08:36 -05:00
gtsiam
e8763b3697 Removed usage of PyYAML for config parsing. (#13883)
* Ignore entire __pycache__ folder instead of individual *.pyc files

* Ignore .mypy_cache in git

* Rework config YAML parsing to use only ruamel.yaml

PyYAML silently overrides keys when encountering duplicates, but ruamel
raises and exception by default. Since we're already using it elsewhere,
dropping PyYAML is an easy choice to make.

* Added EnvString in config to slim down runtime_config()

* Added gitlens to devcontainer

* Automatically call FrigateConfig.runtime_config()

runtime_config needed to be called manually before. Now, it's been
removed, but the same code is run by a pydantic validator.

* Fix handling of missing -segment_time

* Removed type annotation on FrigateConfig's parse

I'd like to keep them, but then mypy complains about some fundamental
errors with how the pydantic model is structured. I'd like to fix it,
but I'd rather work towards moving some of this config to the database.
2024-09-22 10:56:57 -05:00
Nicolas Mowen
6f2924006c Include radeontop in arm build (#13886) 2024-09-22 10:56:30 -05:00
Nicolas Mowen
062c305cd8 Remove context file (#13885) 2024-09-22 08:38:23 -06:00
Josh Hawkins
811da2e159 Clarify live view docs (#13848) 2024-09-20 06:27:15 -06:00
Darryl Sokoloski
c4e2f3bc70 Updated supported cameras: Speco O8P32X (#13698)
Signed-off-by: Darryl Sokoloski <darryl@sokoloski.ca>
2024-09-16 16:17:22 -06:00
Josh Hawkins
bd906a7915 Update docs for another supported autotracking cam (#13753)
* update for Uniview IPC6612SR-X33-VG

* wording
2024-09-15 11:43:11 -06:00
Nicolas Mowen
3df33199bc Add Arc a750 to hardware stats list (#13752) 2024-09-15 11:37:30 -06:00
Josh Hawkins
7ad30f15d5 Add note about onvif cameras without auth (#13721) 2024-09-13 10:43:48 -06:00
mrmorganmurphy
2f38d960d4 Update cameras.md (#13691)
Amcrest IP5M-1190EW does not support autotracking. FOV relative movement not supported.
2024-09-12 07:16:23 -06:00
Nicolas Mowen
2fc58fea81 Add api docs for review api (#13613) 2024-09-07 14:21:38 -05:00
Blake Blackshear
e7dfbf76bb update plus docs for 0.14 (#13604) 2024-09-07 07:28:28 -05:00
OldTyT
94de29187a docs(third_party_extensions.md): added info about frigate telegram (#13584) 2024-09-06 05:42:21 -06:00
Josh Hawkins
a82c1f303b Clarify decoding and the detect role (#13580) 2024-09-05 20:59:47 -05:00
Josh Hawkins
55e1f865d8 Don't allow periods in zone or camera group names (#13400) 2024-08-29 19:58:36 -06:00
Josh Hawkins
3f996cd62c Add portal the live player tooltip (#13389) 2024-08-29 19:58:36 -06:00
Marc Altmann
58a8028485 update go2rtc version in reference config (#13367) 2024-08-29 19:58:36 -06:00
Nicolas Mowen
190ce5ee31 Add tooltip for icons in review event list (#13334) 2024-08-29 19:58:36 -06:00
Blake Blackshear
70aab068fd fix default build (#13321) 2024-08-29 19:58:36 -06:00
Blake Blackshear
617d279419 update actions for release (#13318) 2024-08-29 19:58:36 -06:00
Josh Hawkins
4de088d725 Update discussion templates (#13303)
* Update discussion templates

* camera support go2rtc
2024-08-29 19:58:36 -06:00
Nicolas Mowen
f8fd746678 Fix delayed preview not showing (#13295) 2024-08-29 19:58:36 -06:00
Josh Hawkins
1529ee59fe Fix discussion templates (#13292)
* Fix yaml spacing for discussion templates

* Remove browser question from detectors
2024-08-29 19:58:36 -06:00
Josh Hawkins
19c253b429 Update discussion templates (#13291)
* Revamp support discussion templates

* move text to description

* remove duplicate logs box

* ffprobe on camera support

* longer description on config support
2024-08-29 19:58:36 -06:00
Nicolas Mowen
13bb9dd715 Fix case where user's cgroup says it has 0 cpu cores (#13271) 2024-08-29 19:58:36 -06:00
Nicolas Mowen
9b4602acb3 UI fixes (#13246)
* Fix bad data in stats

* Add support for changes dialog when leaving without saving config editor

* Fix scrolling into view
2024-08-29 19:58:36 -06:00
Nicolas Mowen
e5448110fc Ensure only enabled birdseye cameras are considered active (#13194)
* Ensure only enabled birdseye cameras are considered active

* Cleanup
2024-08-29 19:58:36 -06:00
Nicolas Mowen
4974defe6f Dynamically detect if full screen is supported (#13197) 2024-08-29 19:58:36 -06:00
Nicolas Mowen
65ceadda2b Preview fixes (#13193)
* Handle case where preview was saved late

* fix timing
2024-08-29 19:58:36 -06:00
Josh Hawkins
8b2adb55ed Adjust MSE player playback rate logic (#13164)
* Fix MSE playback rate logic

* don't adjust playback rate if we just started streaming

* memoize onprogress
2024-08-29 19:58:36 -06:00
Nicolas Mowen
58ca44bd15 Fix plus view resetting (#13160) 2024-08-29 19:58:36 -06:00
Josh Hawkins
ef46451b80 Live player fixes (#13143)
* Jump to live when exceeding buffer time threshold in MSE player

* clean up

* Try adjusting playback rate instead of jumping to live

* clean up

* fallback to webrtc if enabled before jsmpeg

* baseline

* clean up

* remove comments

* adaptive playback rate and intelligent switching improvements

* increase logging and reset live mode after camera is no longer active on dashboard only

* jump to live on safari/iOS

* clean up

* clean up

* refactor camera live mode hook

* remove key listener

* resolve conflicts
2024-08-29 19:58:36 -06:00
Josh Hawkins
758b0f9734 Remove dashboard keyboard listener (#13102) 2024-08-29 19:58:36 -06:00
Josh Hawkins
3650000b31 Add shortcut key "r" to mark selected items as reviewed (#13087)
* Add shortcut key "r" to mark selected items as reviewed

* unselect after keypress
2024-08-29 19:58:36 -06:00
Nicolas Mowen
dbd042ca3e Catch case where github sends bad json data (#13077) 2024-08-29 19:58:36 -06:00
Nicolas Mowen
6b9082bdd9 Rename bug report (#13039) 2024-08-29 19:58:36 -06:00
Nicolas Mowen
f9baa3bf20 UI fixes (#13030)
* Fix difficulty overwriting export name

* Fix NaN for score selector
2024-08-29 19:58:36 -06:00
Nicolas Mowen
a75feb7f8f Fix last hour preview (#13027) 2024-08-29 19:58:36 -06:00
Nicolas Mowen
009900b29b Reset recordings when changing the date (#13009) 2024-08-29 19:58:36 -06:00
Nicolas Mowen
dc04cf82d8 Recordings Fixes (#13005)
* If recordings don't exist mark as no recordings

* Fix reloading recordings failing

* Fix mark items not clearing selected

* Cleanup

* Default to last full hour when error occurs

* Remove check

* Cleanup

* Handle empty recordings list case

* Ensure that the start time is within the time range

* Catch other reset cases
2024-08-29 19:58:36 -06:00
Nicolas Mowen
b2c23a367d Hide record switch when disabled (#12997) 2024-08-29 19:58:36 -06:00
Nicolas Mowen
338b59a32e Catch case where recording starts right at end of request (#12956) 2024-08-29 19:58:36 -06:00
Josh Hawkins
07ffd76437 Add pan/pinch/zoom capability on plus snapshots (#12953) 2024-08-29 19:58:36 -06:00
Nicolas Mowen
3eaf9f4011 Catch case where user tries to end definite manual event (#12951)
* Catch case where user tries to end definite manual event

* Formatting
2024-08-29 19:58:36 -06:00
Josh Hawkins
9832831c5e Add confirmation dialog before deleting review items (#12950) 2024-08-29 19:58:36 -06:00
Stavros Kois
d3259c4782 add shortcut and query for fullscreen in live view (#12924)
* add shortcut and query for live view

* Update web/src/views/live/LiveDashboardView.tsx

* Update web/src/views/live/LiveDashboardView.tsx

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>

* Apply suggestions from code review

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>

* Update LiveDashboardView.tsx

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2024-08-29 19:58:36 -06:00
Nicolas Mowen
940c12d9d8 Remove user args from http jpeg (#12909) 2024-08-29 19:58:36 -06:00
Nicolas Mowen
8f2cbe261b Web deps (#12908)
* Update web compnent deps

* Update other web deps
2024-08-29 19:58:36 -06:00
Nicolas Mowen
e86788034d Fix use experimental migrator (#12906) 2024-08-29 19:58:36 -06:00
Nicolas Mowen
4ecc0e15ce Add button to mark review item as reviewed in filmstrip (#12878)
* Add button to mark review item as reviewd in filmstrip

* Add tooltip
2024-08-29 19:58:36 -06:00
Soren L. Hansen
b01ce31903 Fix auth when serving Frigate at a subpath (#12815)
Ensure axios.defaults.baseURL is set when accessing login form.

Drop `/api` prefix in login form's `axios.post` call, since `/api` is
part of the baseURL.

Redirect to subpath on succesful authentication.

Prepend subpath to default logout url.

Fixes #12814
2024-08-29 19:58:36 -06:00
Josh Hawkins
87b69c373a Persist live view muted/unmuted for session only (#12727)
* Persist live view muted/unmuted for session only

* consistent naming
2024-08-29 19:58:36 -06:00
Josh Hawkins
07b3160dff Add right click to delete points in desktop mask/zone editor (#12744) 2024-08-29 19:58:36 -06:00
Josh Hawkins
096e2791f5 Ensure review card icon color for event view is visible in light mode (#12812) 2024-08-29 19:58:36 -06:00
Marc Altmann
9d456ccfcf fix default model for rknn detector (#12807) 2024-08-29 19:58:36 -06:00
Nicolas Mowen
ad5c3741e9 Add camera name to audio debug line (#12799)
* Add camera name to audio debug line

* Formatting
2024-08-29 19:58:36 -06:00
Nicolas Mowen
fe188bd646 Handle case where user stops scrubbing but remains hovering (#12794)
* Handle case where user stops scrubbing but remains hovering

* Add type
2024-08-29 19:58:36 -06:00
Josh Hawkins
f47984818f Ensure review cameras are sorted by config ui order if specified (#12789) 2024-08-29 19:58:36 -06:00
Nicolas Mowen
7b274b6974 Use camera status to get state of camera config (#12787)
* Use camera status to get state of camera config

* Fix spelling
2024-08-29 19:58:36 -06:00
Nicolas Mowen
b1806b0a7c Handle case where sub label was null (#12785) 2024-08-29 19:58:36 -06:00
Nicolas Mowen
ff2e46650c Update version 2024-08-29 19:58:36 -06:00
Nicolas Mowen
69fe6cdc05 Fix iOS export buttons (#12755)
* Fix iOS export buttons

* Use layering instead of z index
2024-08-29 19:58:36 -06:00
Josh Hawkins
b7e0d14b83 Only use dense property on phones for motion review timeline (#12768) 2024-08-29 19:58:36 -06:00
Josh Hawkins
7db6ed9ad5 Use radix css var to limit desktop menu height (#12743) 2024-08-29 19:58:36 -06:00
Josh Hawkins
da0f63f095 Fix large tablet recording view layout (#12753) 2024-08-29 19:58:36 -06:00
cvroque
90221e8c94 Remove duplicated text (#13416) 2024-08-29 09:10:47 -06:00
Josh Hawkins
37680c317c Change wording of offline message to account for broker lwt timeout (#13403) 2024-08-28 09:22:57 -05:00
Josh Hawkins
70ea6fc9a1 Update live view docs with camera firmware settings recommendations (#13370)
* Update live view docs with camera firmware settings recommendations

* video/audio

* capitalization

* Video only cams

* clarify higher iframes

* update wording

* fix wording

* Add note on camera specific page

* change note
2024-08-27 07:00:54 -06:00
Nicolas Mowen
67e692a7f3 Add edgetpu docs for synology specific issue (#13335) 2024-08-25 07:56:05 -05:00
ghxstxch
34382ac38e Update cameras.md (#13309) 2024-08-24 06:07:25 -06:00
Nicolas Mowen
b94b08a33c Add comment about global zones behavior (#13269) 2024-08-22 07:47:53 -05:00
Nicolas Mowen
540d66af57 Update reference config to include motion enabled field (#13255) 2024-08-21 20:10:12 -05:00
Josh Hawkins
a2deeb0d12 Update live player docs (#13245)
* Clarify live modes in 0.14

* change column name

* clarify wording
2024-08-21 08:01:15 -05:00
Peter Riemersma
22fe261dd6 Update cameras.md (#13218) 2024-08-20 13:52:35 -06:00
Josh Hawkins
b44354ad29 Update configuring go2rtc docs to reflect 0.14 changes (#13147) 2024-08-17 15:04:12 -06:00
elreydetoda
3ffbdb35a2 updating HACS installation instructions (#13136) 2024-08-17 06:14:45 -06:00
Emil Sandnabba
0504e9ef79 Update reverse proxy documentation (#13075)
* Cleanup the reverse proxy overview

* Adding a Traefik example

* Adding a note about TLS

* Update docs/docs/guides/reverse_proxy.md

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>

* Update docs/docs/guides/reverse_proxy.md

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>

* Update docs/docs/guides/reverse_proxy.md

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

* Update docs/docs/guides/reverse_proxy.md

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
2024-08-14 15:51:15 -06:00
Nicolas Mowen
b309287087 Add docs for installing Frigate as PWA (#12995)
* Add docs for installing Frigate as PWA

* Add to sidebar
2024-08-12 08:21:02 -05:00
axyzs
e891f2ad6d correct github colab url (#12948) 2024-08-11 07:21:39 -06:00
Josh Hawkins
9b1fb33ac6 Fix camera group icon name in reference config (#12883) 2024-08-09 08:41:12 -06:00
Blake Blackshear
8a099b4ae5 Merge pull request #11419 from blakeblackshear/dev
0.14 Release
2024-08-08 08:43:29 -05:00
jameson_uk
2cdd483126 Update automation (#12487)
`data_template` has been deprecated for sometime in HA an no longer works.  This should just be `data`
2024-07-18 16:30:24 -06:00
236 changed files with 17056 additions and 6315 deletions

View File

@@ -52,7 +52,8 @@
"csstools.postcss",
"blanu.vscode-styled-jsx",
"bradlc.vscode-tailwindcss",
"charliermarsh.ruff"
"charliermarsh.ruff",
"eamodio.gitlens"
],
"settings": {
"remote.autoForwardPorts": false,

View File

@@ -90,6 +90,9 @@ body:
- HassOS Addon
- Docker Compose
- Docker CLI
- Proxmox via Docker
- Proxmox via TTeck Script
- Windows WSL2
validations:
required: true
- type: dropdown
@@ -102,7 +105,7 @@ body:
- TensorRT
- RKNN
- Other
- CPU (no Coral)
- CPU (no coral)
validations:
required: true
- type: dropdown

View File

@@ -76,6 +76,17 @@ body:
- HassOS Addon
- Docker Compose
- Docker CLI
- Proxmox via Docker
- Proxmox via TTeck Script
- Windows WSL2
validations:
required: true
- type: textarea
id: docker
attributes:
label: docker-compose file or Docker CLI command
description: This will be automatically formatted into code, so no need for backticks.
render: yaml
validations:
required: true
- type: dropdown

View File

@@ -48,28 +48,6 @@ body:
render: shell
validations:
required: true
- type: textarea
id: go2rtclogs
attributes:
label: Relevant go2rtc log output
description: Please copy and paste any relevant go2rtc log output. Include logs before and after your exact error when possible. Logs can be viewed via the Frigate UI, Docker, or the go2rtc dashboard. This will be automatically formatted into code, so no need for backticks.
render: shell
validations:
required: true
- type: dropdown
id: os
attributes:
label: Operating system
options:
- HassOS
- Debian
- Other Linux
- Proxmox
- UNRAID
- Windows
- Other
validations:
required: true
- type: dropdown
id: install-method
attributes:
@@ -78,6 +56,22 @@ body:
- HassOS Addon
- Docker Compose
- Docker CLI
- Proxmox via Docker
- Proxmox via TTeck Script
- Windows WSL2
validations:
required: true
- type: dropdown
id: object-detector
attributes:
label: Object Detector
options:
- Coral
- OpenVino
- TensorRT
- RKNN
- Other
- CPU (no coral)
validations:
required: true
- type: dropdown

View File

@@ -68,20 +68,6 @@ body:
label: Frigate stats
description: Output from frigate's /api/stats endpoint
render: json
- type: dropdown
id: os
attributes:
label: Operating system
options:
- HassOS
- Debian
- Other Linux
- Proxmox
- UNRAID
- Windows
- Other
validations:
required: true
- type: dropdown
id: install-method
attributes:
@@ -90,6 +76,30 @@ body:
- HassOS Addon
- Docker Compose
- Docker CLI
- Proxmox via Docker
- Proxmox via TTeck Script
- Windows WSL2
validations:
required: true
- type: textarea
id: docker
attributes:
label: docker-compose file or Docker CLI command
description: This will be automatically formatted into code, so no need for backticks.
render: yaml
validations:
required: true
- type: dropdown
id: object-detector
attributes:
label: Object Detector
options:
- Coral
- OpenVino
- TensorRT
- RKNN
- Other
- CPU (no coral)
validations:
required: true
- type: dropdown

View File

@@ -24,12 +24,6 @@ body:
description: Visible on the System page in the Web UI. Please include the full version including the build identifier (eg. 0.14.0-ea36ds1)
validations:
required: true
- type: input
attributes:
label: In which browser(s) are you experiencing the issue with?
placeholder: Google Chrome 88.0.4324.150
description: >
Provide the full name and don't forget to add the version!
- type: textarea
id: config
attributes:
@@ -70,20 +64,6 @@ body:
render: shell
validations:
required: true
- type: dropdown
id: os
attributes:
label: Operating system
options:
- HassOS
- Debian
- Other Linux
- Proxmox
- UNRAID
- Windows
- Other
validations:
required: true
- type: dropdown
id: install-method
attributes:
@@ -92,6 +72,22 @@ body:
- HassOS Addon
- Docker Compose
- Docker CLI
- Proxmox via Docker
- Proxmox via TTeck Script
- Windows WSL2
validations:
required: true
- type: dropdown
id: object-detector
attributes:
label: Object Detector
options:
- Coral
- OpenVino
- TensorRT
- RKNN
- Other
- CPU (no coral)
validations:
required: true
- type: dropdown

31
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,31 @@
## Proposed change
<!--
Describe what this pull request does and how it will benefit users of Frigate.
Please describe in detail any considerations, breaking changes, etc. that are
made in this pull request.
-->
## Type of change
- [ ] Dependency upgrade
- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New feature
- [ ] Breaking change (fix/feature causing existing functionality to break)
- [ ] Code quality improvements to existing code
## Additional information
- This PR fixes or closes issue: fixes #
- This PR is related to issue:
## Checklist
<!--
Put an `x` in the boxes that apply.
-->
- [ ] The code change is tested and works locally.
- [ ] Local tests pass. **Your PR cannot be merged unless tests pass**
- [ ] There is no commented out code in this PR.
- [ ] The code has been formatted using Ruff (`ruff format frigate`)

View File

@@ -205,7 +205,7 @@ jobs:
with:
string: ${{ github.repository }}
- name: Log in to the Container registry
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567
with:
registry: ghcr.io
username: ${{ github.actor }}

View File

@@ -16,7 +16,7 @@ jobs:
with:
string: ${{ github.repository }}
- name: Log in to the Container registry
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567
with:
registry: ghcr.io
username: ${{ github.actor }}

3
.gitignore vendored
View File

@@ -1,5 +1,6 @@
.DS_Store
*.pyc
__pycache__
.mypy_cache
*.swp
debug
.vscode/*

View File

@@ -4,6 +4,7 @@ from statistics import mean
import numpy as np
import frigate.util as util
from frigate.config import DetectorTypeEnum
from frigate.object_detection import (
ObjectDetectProcess,
@@ -90,7 +91,7 @@ edgetpu_process_2 = ObjectDetectProcess(
)
for x in range(0, 10):
camera_process = mp.Process(
camera_process = util.Process(
target=start, args=(x, 300, detection_queue, events[str(x)])
)
camera_process.daemon = True

View File

@@ -50,7 +50,7 @@ RUN PYTHON_VERSION=$(python3 --version 2>&1 | awk '{print $2}' | cut -d. -f1,2)
RUN . /etc/environment && \
git clone https://github.com/hailo-ai/hailort.git /opt/hailort && \
cd /opt/hailort && \
git checkout v4.17.0 && \
git checkout v4.18.0 && \
cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release -DHAILO_BUILD_PYBIND=1 -DPYBIND11_PYTHON_VERSION=${PYTHON_VERSION} && \
cmake --build build --config release --target libhailort && \
cmake --build build --config release --target _pyhailort && \
@@ -91,7 +91,7 @@ RUN pip3 install -U /deps/hailo-wheels/*.whl
RUN . /etc/environment && \
mv /usr/local/lib/python${PYTHON_VERSION}/dist-packages/hailo_platform/pyhailort/libhailort.so /usr/lib/${CC} && \
cd /usr/lib/${CC}/ && \
ln -s libhailort.so libhailort.so.4.17.0
ln -s libhailort.so libhailort.so.4.18.0
# Copy base files from the rootfs stage
COPY --from=rootfs / /

View File

@@ -2,7 +2,7 @@
# Update package list and install dependencies
sudo apt-get update
sudo apt-get install -y build-essential cmake git wget linux-modules-extra-$(uname -r)
sudo apt-get install -y build-essential cmake git wget
arch=$(uname -m)
@@ -13,7 +13,7 @@ else
fi
# Clone the HailoRT driver repository
git clone --depth 1 --branch v4.17.0 https://github.com/hailo-ai/hailort-drivers.git
git clone --depth 1 --branch v4.18.0 https://github.com/hailo-ai/hailort-drivers.git
# Build and install the HailoRT driver
cd hailort-drivers/linux/pcie
@@ -23,9 +23,21 @@ sudo make install
# Load the Hailo PCI driver
sudo modprobe hailo_pci
if [ $? -ne 0 ]; then
echo "Unable to load hailo_pci module, common reasons for this are:"
echo "- Key was rejected by service: Secure Boot is enabling disallowing install."
echo "- Permissions are not setup correctly."
exit 1
fi
# Download and install the firmware
cd ../../
./download_firmware.sh
# verify the firmware folder is present
if [ ! -d /lib/firmware/hailo ]; then
sudo mkdir /lib/firmware/hailo
fi
sudo mv hailo8_fw.4.17.0.bin /lib/firmware/hailo/hailo8_fw.bin
# Install udev rules
@@ -33,3 +45,4 @@ sudo cp ./linux/pcie/51-hailo-udev.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules && sudo udevadm trigger
echo "HailoRT driver installation complete."
echo "reboot your system to load the firmware!"

View File

@@ -30,6 +30,16 @@ RUN --mount=type=tmpfs,target=/tmp --mount=type=tmpfs,target=/var/cache/apt \
--mount=type=cache,target=/root/.ccache \
/deps/build_nginx.sh
FROM wget AS sqlite-vec
ARG DEBIAN_FRONTEND
# Build sqlite_vec from source
COPY docker/main/build_sqlite_vec.sh /deps/build_sqlite_vec.sh
RUN --mount=type=tmpfs,target=/tmp --mount=type=tmpfs,target=/var/cache/apt \
--mount=type=bind,source=docker/main/build_sqlite_vec.sh,target=/deps/build_sqlite_vec.sh \
--mount=type=cache,target=/root/.ccache \
/deps/build_sqlite_vec.sh
FROM scratch AS go2rtc
ARG TARGETARCH
WORKDIR /rootfs/usr/local/go2rtc/bin
@@ -163,7 +173,7 @@ RUN wget -q https://bootstrap.pypa.io/get-pip.py -O get-pip.py \
COPY docker/main/requirements.txt /requirements.txt
RUN pip3 install -r /requirements.txt
# Build pysqlite3 from source to support ChromaDB
# Build pysqlite3 from source
COPY docker/main/build_pysqlite3.sh /build_pysqlite3.sh
RUN /build_pysqlite3.sh
@@ -177,6 +187,7 @@ RUN pip3 wheel --no-deps --wheel-dir=/wheels-post -r /requirements-wheels-post.t
# Collect deps in a single layer
FROM scratch AS deps-rootfs
COPY --from=nginx /usr/local/nginx/ /usr/local/nginx/
COPY --from=sqlite-vec /usr/local/lib/ /usr/local/lib/
COPY --from=go2rtc /rootfs/ /
COPY --from=libusb-build /usr/local/lib /usr/local/lib
COPY --from=tempio /rootfs/ /
@@ -197,12 +208,11 @@ ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
ENV NVIDIA_VISIBLE_DEVICES=all
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
# Turn off Chroma Telemetry: https://docs.trychroma.com/telemetry#opting-out
ENV ANONYMIZED_TELEMETRY=False
# Allow resetting the chroma database
ENV ALLOW_RESET=True
# Disable tokenizer parallelism warning
# https://stackoverflow.com/questions/62691279/how-to-disable-tokenizers-parallelism-true-false-warning/72926996#72926996
ENV TOKENIZERS_PARALLELISM=true
# https://github.com/huggingface/transformers/issues/27214
ENV TRANSFORMERS_NO_ADVISORY_WARNINGS=1
ENV PATH="/usr/local/go2rtc/bin:/usr/local/tempio/bin:/usr/local/nginx/sbin:${PATH}"
ENV LIBAVFORMAT_VERSION_MAJOR=60
@@ -239,7 +249,7 @@ ENV S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0
ENTRYPOINT ["/init"]
CMD []
HEALTHCHECK --start-period=120s --start-interval=5s --interval=15s --timeout=5s --retries=3 \
HEALTHCHECK --start-period=300s --start-interval=5s --interval=15s --timeout=5s --retries=3 \
CMD curl --fail --silent --show-error http://127.0.0.1:5000/api/version || exit 1
# Frigate deps with Node.js and NPM for devcontainer

31
docker/main/build_sqlite_vec.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/bash
set -euxo pipefail
SQLITE_VEC_VERSION="0.1.3"
cp /etc/apt/sources.list /etc/apt/sources.list.d/sources-src.list
sed -i 's|deb http|deb-src http|g' /etc/apt/sources.list.d/sources-src.list
apt-get update
apt-get -yqq build-dep sqlite3 gettext git
mkdir /tmp/sqlite_vec
# Grab the sqlite_vec source code.
wget -nv https://github.com/asg017/sqlite-vec/archive/refs/tags/v${SQLITE_VEC_VERSION}.tar.gz
tar -zxf v${SQLITE_VEC_VERSION}.tar.gz -C /tmp/sqlite_vec
cd /tmp/sqlite_vec/sqlite-vec-${SQLITE_VEC_VERSION}
mkdir -p vendor
wget -O sqlite-amalgamation.zip https://www.sqlite.org/2024/sqlite-amalgamation-3450300.zip
unzip sqlite-amalgamation.zip
mv sqlite-amalgamation-3450300/* vendor/
rmdir sqlite-amalgamation-3450300
rm sqlite-amalgamation.zip
# build loadable module
make loadable
# install it
cp dist/vec0.* /usr/local/lib

View File

@@ -13,6 +13,7 @@ apt-get -qq install --no-install-recommends -y \
python3.9 \
python3-pip \
curl \
lsof \
jq \
nethogs
@@ -44,7 +45,7 @@ if [[ "${TARGETARCH}" == "amd64" ]]; then
wget -qO btbn-ffmpeg.tar.xz "https://github.com/NickM-27/FFmpeg-Builds/releases/download/autobuild-2022-07-31-12-37/ffmpeg-n5.1-2-g915ef932a3-linux64-gpl-5.1.tar.xz"
tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/5.0 --strip-components 1
rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/5.0/doc /usr/lib/ffmpeg/5.0/bin/ffplay
wget -qO btbn-ffmpeg.tar.xz "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2024-09-19-12-51/ffmpeg-n7.0.2-18-g3e6cec1286-linux64-gpl-7.0.tar.xz"
wget -qO btbn-ffmpeg.tar.xz "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2024-09-30-15-36/ffmpeg-n7.1-linux64-gpl-7.1.tar.xz"
tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/7.0 --strip-components 1
rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/7.0/doc /usr/lib/ffmpeg/7.0/bin/ffplay
fi
@@ -56,7 +57,7 @@ if [[ "${TARGETARCH}" == "arm64" ]]; then
wget -qO btbn-ffmpeg.tar.xz "https://github.com/NickM-27/FFmpeg-Builds/releases/download/autobuild-2022-07-31-12-37/ffmpeg-n5.1-2-g915ef932a3-linuxarm64-gpl-5.1.tar.xz"
tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/5.0 --strip-components 1
rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/5.0/doc /usr/lib/ffmpeg/5.0/bin/ffplay
wget -qO btbn-ffmpeg.tar.xz "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2024-09-19-12-51/ffmpeg-n7.0.2-18-g3e6cec1286-linuxarm64-gpl-7.0.tar.xz"
wget -qO btbn-ffmpeg.tar.xz "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2024-09-30-15-36/ffmpeg-n7.1-linuxarm64-gpl-7.1.tar.xz"
tar -xf btbn-ffmpeg.tar.xz -C /usr/lib/ffmpeg/7.0 --strip-components 1
rm -rf btbn-ffmpeg.tar.xz /usr/lib/ffmpeg/7.0/doc /usr/lib/ffmpeg/7.0/bin/ffplay
fi
@@ -91,7 +92,7 @@ fi
if [[ "${TARGETARCH}" == "arm64" ]]; then
apt-get -qq install --no-install-recommends --no-install-suggests -y \
libva-drm2 mesa-va-drivers
libva-drm2 mesa-va-drivers radeontop
fi
# install vulkan

View File

@@ -1,9 +1,13 @@
click == 8.1.*
Flask == 3.0.*
Flask_Limiter == 3.8.*
# FastAPI
starlette-context == 0.3.6
fastapi == 0.115.0
uvicorn == 0.30.*
slowapi == 0.1.9
imutils == 0.5.*
joserfc == 1.0.*
markupsafe == 2.1.*
pathvalidate == 3.2.*
markupsafe == 3.0.*
mypy == 1.6.1
numpy == 1.26.*
onvif_zeep == 0.2.12
@@ -15,12 +19,10 @@ peewee_migrate == 1.13.*
psutil == 5.9.*
pydantic == 2.8.*
git+https://github.com/fbcotter/py3nvml#egg=py3nvml
PyYAML == 6.0.*
pytz == 2024.1
pyzmq == 26.2.*
ruamel.yaml == 0.18.*
tzlocal == 5.2
types-PyYAML == 6.0.*
requests == 2.32.*
types-requests == 2.32.*
scipy == 1.13.*
@@ -31,12 +33,12 @@ unidecode == 1.3.*
# OpenVino (ONNX installed in wheels-post)
openvino == 2024.3.*
# Embeddings
chromadb == 0.5.0
transformers == 4.45.*
onnx_clip == 4.0.*
# Generative AI
google-generativeai == 0.6.*
ollama == 0.2.*
openai == 1.30.*
google-generativeai == 0.8.*
ollama == 0.3.*
openai == 1.51.*
# push notifications
py-vapid == 1.9.*
pywebpush == 2.0.*

View File

@@ -1 +0,0 @@
chroma-pipeline

View File

@@ -1,4 +0,0 @@
#!/command/with-contenv bash
# shellcheck shell=bash
exec logutil-service /dev/shm/logs/chroma

View File

@@ -1,28 +0,0 @@
#!/command/with-contenv bash
# shellcheck shell=bash
# Take down the S6 supervision tree when the service exits
set -o errexit -o nounset -o pipefail
# Logs should be sent to stdout so that s6 can collect them
declare exit_code_container
exit_code_container=$(cat /run/s6-linux-init-container-results/exitcode)
readonly exit_code_container
readonly exit_code_service="${1}"
readonly exit_code_signal="${2}"
readonly service="ChromaDB"
echo "[INFO] Service ${service} exited with code ${exit_code_service} (by signal ${exit_code_signal})"
if [[ "${exit_code_service}" -eq 256 ]]; then
if [[ "${exit_code_container}" -eq 0 ]]; then
echo $((128 + exit_code_signal)) >/run/s6-linux-init-container-results/exitcode
fi
elif [[ "${exit_code_service}" -ne 0 ]]; then
if [[ "${exit_code_container}" -eq 0 ]]; then
echo "${exit_code_service}" >/run/s6-linux-init-container-results/exitcode
fi
fi
exec /run/s6/basedir/bin/halt

View File

@@ -1,27 +0,0 @@
#!/command/with-contenv bash
# shellcheck shell=bash
# Start the Frigate service
set -o errexit -o nounset -o pipefail
# Logs should be sent to stdout so that s6 can collect them
# Tell S6-Overlay not to restart this service
s6-svc -O .
search_enabled=`python3 /usr/local/semantic_search/get_search_settings.py | jq -r .enabled`
# Replace the bash process with the Frigate process, redirecting stderr to stdout
exec 2>&1
if [[ "$search_enabled" == 'true' ]]; then
echo "[INFO] Starting ChromaDB..."
exec /usr/local/chroma run --path /config/chroma --host 127.0.0.1
else
while true
do
sleep 9999
continue
done
exit 0
fi

View File

@@ -1 +0,0 @@
longrun

View File

@@ -4,7 +4,7 @@
set -o errexit -o nounset -o pipefail
dirs=(/dev/shm/logs/frigate /dev/shm/logs/go2rtc /dev/shm/logs/nginx /dev/shm/logs/certsync /dev/shm/logs/chroma)
dirs=(/dev/shm/logs/frigate /dev/shm/logs/go2rtc /dev/shm/logs/nginx /dev/shm/logs/certsync)
mkdir -p "${dirs[@]}"
chown nobody:nogroup "${dirs[@]}"

View File

@@ -1,14 +0,0 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-s
__import__("pysqlite3")
import re
import sys
sys.modules["sqlite3"] = sys.modules.pop("pysqlite3")
from chromadb.cli.cli import app
if __name__ == "__main__":
sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
sys.exit(app())

View File

@@ -6,7 +6,7 @@ import shutil
import sys
from pathlib import Path
import yaml
from ruamel.yaml import YAML
sys.path.insert(0, "/opt/frigate")
from frigate.const import (
@@ -18,6 +18,7 @@ from frigate.ffmpeg_presets import parse_preset_hardware_acceleration_encode
sys.path.remove("/opt/frigate")
yaml = YAML()
FRIGATE_ENV_VARS = {k: v for k, v in os.environ.items() if k.startswith("FRIGATE_")}
# read docker secret files as env vars too
@@ -40,7 +41,7 @@ try:
raw_config = f.read()
if config_file.endswith((".yaml", ".yml")):
config: dict[str, any] = yaml.safe_load(raw_config)
config: dict[str, any] = yaml.load(raw_config)
elif config_file.endswith(".json"):
config: dict[str, any] = json.loads(raw_config)
except FileNotFoundError:

View File

@@ -104,6 +104,8 @@ http {
add_header Cache-Control "no-store";
expires off;
keepalive_disable safari;
}
location /stream/ {
@@ -224,7 +226,7 @@ http {
location ~* /api/.*\.(jpg|jpeg|png|webp|gif)$ {
include auth_request.conf;
rewrite ^/api/(.*)$ $1 break;
rewrite ^/api/(.*)$ /$1 break;
proxy_pass http://frigate_api;
include proxy.conf;
}

View File

@@ -3,7 +3,9 @@
import json
import os
import yaml
from ruamel.yaml import YAML
yaml = YAML()
config_file = os.environ.get("CONFIG_FILE", "/config/config.yml")
@@ -17,7 +19,7 @@ try:
raw_config = f.read()
if config_file.endswith((".yaml", ".yml")):
config: dict[str, any] = yaml.safe_load(raw_config)
config: dict[str, any] = yaml.load(raw_config)
elif config_file.endswith(".json"):
config: dict[str, any] = json.loads(raw_config)
except FileNotFoundError:

View File

@@ -1,28 +0,0 @@
"""Prints the semantic_search config as json to stdout."""
import json
import os
import yaml
config_file = os.environ.get("CONFIG_FILE", "/config/config.yml")
# Check if we can use .yaml instead of .yml
config_file_yaml = config_file.replace(".yml", ".yaml")
if os.path.isfile(config_file_yaml):
config_file = config_file_yaml
try:
with open(config_file) as f:
raw_config = f.read()
if config_file.endswith((".yaml", ".yml")):
config: dict[str, any] = yaml.safe_load(raw_config)
elif config_file.endswith(".json"):
config: dict[str, any] = json.loads(raw_config)
except FileNotFoundError:
config: dict[str, any] = {}
search_config: dict[str, any] = config.get("semantic_search", {"enabled": False})
print(json.dumps(search_config))

View File

@@ -83,6 +83,7 @@ ARG AMDGPU
COPY --from=rocm /opt/rocm-$ROCM/bin/rocminfo /opt/rocm-$ROCM/bin/migraphx-driver /opt/rocm-$ROCM/bin/
COPY --from=rocm /opt/rocm-$ROCM/share/miopen/db/*$AMDGPU* /opt/rocm-$ROCM/share/miopen/db/
COPY --from=rocm /opt/rocm-$ROCM/share/miopen/db/*gfx908* /opt/rocm-$ROCM/share/miopen/db/
COPY --from=rocm /opt/rocm-$ROCM/lib/rocblas/library/*$AMDGPU* /opt/rocm-$ROCM/lib/rocblas/library/
COPY --from=rocm /opt/rocm-dist/ /
COPY --from=debian-build /opt/rocm/lib/migraphx.cpython-39-x86_64-linux-gnu.so /opt/rocm-$ROCM/lib/

View File

@@ -1,5 +1,10 @@
# Website
This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator.
This website is built using [Docusaurus 3.5](https://docusaurus.io/docs), a modern static website generator.
For installation and contributing instructions, please follow the [Contributing Docs](https://docs.frigate.video/development/contributing).
# Development
1. Run `npm i` to install dependencies
2. Run `npm run start` to start the website

View File

@@ -183,7 +183,7 @@ To do this:
3. Give `go2rtc` execute permission.
4. Restart Frigate and the custom version will be used, you can verify by checking go2rtc logs.
## Validating your config.yaml file updates
## Validating your config.yml file updates
When frigate starts up, it checks whether your config file is valid, and if it is not, the process exits. To minimize interruptions when updating your config, you have three options -- you can edit the config via the WebUI which has built in validation, use the config API, or you can validate on the command line using the frigate docker container.
@@ -211,5 +211,5 @@ docker run \
--entrypoint python3 \
ghcr.io/blakeblackshear/frigate:stable \
-u -m frigate \
--validate_config
--validate-config
```

View File

@@ -26,7 +26,7 @@ In the event that you are locked out of your instance, you can tell Frigate to r
## Login failure rate limiting
In order to limit the risk of brute force attacks, rate limiting is available for login failures. This is implemented with Flask-Limiter, and the string notation for valid values is available in [the documentation](https://flask-limiter.readthedocs.io/en/stable/configuration.html#rate-limit-string-notation).
In order to limit the risk of brute force attacks, rate limiting is available for login failures. This is implemented with SlowApi, and the string notation for valid values is available in [the documentation](https://limits.readthedocs.io/en/stable/quickstart.html#examples).
For example, `1/second;5/minute;20/hour` will rate limit the login endpoint when failures occur more than:

View File

@@ -9,6 +9,12 @@ This page makes use of presets of FFmpeg args. For more information on presets,
:::
:::note
Many cameras support encoding options which greatly affect the live view experience, see the [Live view](/configuration/live) page for more info.
:::
## MJPEG Cameras
Note that mjpeg cameras require encoding the video into h264 for recording, and restream roles. This will use significantly more CPU than if the cameras supported h264 feeds directly. It is recommended to use the restream role to create an h264 restream and then use that as the source for ffmpeg.

View File

@@ -79,29 +79,41 @@ cameras:
If the ONVIF connection is successful, PTZ controls will be available in the camera's WebUI.
:::tip
If your ONVIF camera does not require authentication credentials, you may still need to specify an empty string for `user` and `password`, eg: `user: ""` and `password: ""`.
:::
An ONVIF-capable camera that supports relative movement within the field of view (FOV) can also be configured to automatically track moving objects and keep them in the center of the frame. For autotracking setup, see the [autotracking](autotracking.md) docs.
## ONVIF PTZ camera recommendations
This list of working and non-working PTZ cameras is based on user feedback.
| Brand or specific camera | PTZ Controls | Autotracking | Notes |
| ------------------------ | :----------: | :----------: | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Amcrest | ✅ | ✅ | ⛔️ Generally, Amcrest should work, but some older models (like the common IP2M-841) don't support autotracking |
| Amcrest ASH21 | | ❌ | No ONVIF support |
| Ctronics PTZ | ✅ | ❌ | |
| Dahua | ✅ | | |
| Foscam R5 | ✅ | ❌ | |
| Hanwha XNP-6550RH | ✅ | | |
| Hikvision | ✅ | | Incomplete ONVIF support (MoveStatus won't update even on latest firmware) - reported with HWP-N4215IH-DE and DS-2DE3304W-DE, but likely others |
| Reolink 511WA | ✅ | ❌ | Zoom only |
| Reolink E1 Pro | ✅ | ❌ | |
| Reolink E1 Zoom | ✅ | ❌ | |
| Reolink RLC-823A 16x | ✅ | | |
| Sunba 405-D20X | ✅ | ❌ | |
| Tapo | ✅ | ❌ | Many models supported, ONVIF Service Port: 2020 |
| Uniview IPC672LR-AX4DUPK | ✅ | ❌ | Firmware says FOV relative movement is supported, but camera doesn't actually move when sending ONVIF commands |
| Vikylin PTZ-2804X-I2 | | ❌ | Incomplete ONVIF support |
| Brand or specific camera | PTZ Controls | Autotracking | Notes |
| ---------------------------- | :----------: | :----------: | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Amcrest | ✅ | ✅ | ⛔️ Generally, Amcrest should work, but some older models (like the common IP2M-841) don't support autotracking |
| Amcrest ASH21 | | ❌ | ONVIF service port: 80 |
| Amcrest IP4M-S2112EW-AI | ✅ | ❌ | FOV relative movement not supported. |
| Amcrest IP5M-1190EW | ✅ | | ONVIF Port: 80. FOV relative movement not supported. |
| Ctronics PTZ | ✅ | ❌ | |
| Dahua | ✅ | | |
| Dahua DH-SD2A500HB | ✅ | ❌ | |
| Foscam R5 | ✅ | ❌ | |
| Hanwha XNP-6550RH | ✅ | ❌ | |
| Hikvision | ✅ | ❌ | Incomplete ONVIF support (MoveStatus won't update even on latest firmware) - reported with HWP-N4215IH-DE and DS-2DE3304W-DE, but likely others |
| Hikvision DS-2DE3A404IWG-E/W | ✅ | | |
| Reolink 511WA | ✅ | ❌ | Zoom only |
| Reolink E1 Pro | ✅ | ❌ | |
| Reolink E1 Zoom | ✅ | ❌ | |
| Reolink RLC-823A 16x | | ❌ | |
| Speco O8P32X | ✅ | ❌ | |
| Sunba 405-D20X | ✅ | ❌ | |
| Tapo | ✅ | ❌ | Many models supported, ONVIF Service Port: 2020 |
| Uniview IPC672LR-AX4DUPK | ✅ | ❌ | Firmware says FOV relative movement is supported, but camera doesn't actually move when sending ONVIF commands |
| Uniview IPC6612SR-X33-VG | ✅ | ✅ | Leave `calibrate_on_startup` as `False`. A user has reported that zooming with `absolute` is working. |
| Vikylin PTZ-2804X-I2 | ❌ | ❌ | Incomplete ONVIF support |
## Setting up camera groups

View File

@@ -100,6 +100,28 @@ genai:
model: gpt-4o
```
## Azure OpenAI
Microsoft offers several vision models through Azure OpenAI. A subscription is required.
### Supported Models
You must use a vision capable model with Frigate. Current model variants can be found [in their documentation](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models). At the time of writing, this includes `gpt-4o` and `gpt-4-turbo`.
### Create Resource and Get API Key
To start using Azure OpenAI, you must first [create a resource](https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal#create-a-resource). You'll need your API key and resource URL, which must include the `api-version` parameter (see the example below). The model field is not required in your configuration as the model is part of the deployment name you chose when deploying the resource.
### Configuration
```yaml
genai:
enabled: True
provider: azure_openai
base_url: https://example-endpoint.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2023-03-15-preview
api_key: "{FRIGATE_OPENAI_API_KEY}"
```
## Custom Prompts
Frigate sends multiple frames from the tracked object along with a prompt to your Generative AI provider asking it to generate a description. The default prompt is as follows:
@@ -128,16 +150,24 @@ genai:
car: "Label the primary vehicle in these images with just the name of the company if it is a delivery vehicle, or the color make and model."
```
Prompts can also be overriden at the camera level to provide a more detailed prompt to the model about your specific camera, if you desire.
Prompts can also be overriden at the camera level to provide a more detailed prompt to the model about your specific camera, if you desire. By default, descriptions will be generated for all tracked objects and all zones. But you can also optionally specify `objects` and `required_zones` to only generate descriptions for certain tracked objects or zones.
Optionally, you can generate the description using a snapshot (if enabled) by setting `use_snapshot` to `True`. By default, this is set to `False`, which sends the thumbnails collected over the object's lifetime to the model. Using a snapshot provides the AI with a higher-resolution image (typically downscaled by the AI itself), but the trade-off is that only a single image is used, which might limit the model's ability to determine object movement or direction.
```yaml
cameras:
front_door:
genai:
use_snapshot: True
prompt: "Describe the {label} in these images from the {camera} security camera at the front door of a house, aimed outward toward the street."
object_prompts:
person: "Describe the main person in these images (gender, age, clothing, activity, etc). Do not include where the activity is occurring (sidewalk, concrete, driveway, etc). If delivering a package, include the company the package is from."
cat: "Describe the cat in these images (color, size, tail). Indicate whether or not the cat is by the flower pots. If the cat is chasing a mouse, make up a name for the mouse."
objects:
- person
- cat
required_zones:
- steps
```
### Experiment with prompts

View File

@@ -65,6 +65,8 @@ Or map in all the `/dev/video*` devices.
## Intel-based CPUs
:::info
**Recommended hwaccel Preset**
| CPU Generation | Intel Driver | Recommended Preset | Notes |
@@ -74,11 +76,13 @@ Or map in all the `/dev/video*` devices.
| gen13+ | iHD / Xe | preset-intel-qsv-* | |
| Intel Arc GPU | iHD / Xe | preset-intel-qsv-* | |
:::
:::note
The default driver is `iHD`. You may need to change the driver to `i965` by adding the following environment variable `LIBVA_DRIVER_NAME=i965` to your docker-compose file or [in the `frigate.yaml` for HA OS users](advanced.md#environment_vars).
See [The Intel Docs](https://www.intel.com/content/www/us/en/support/articles/000005505/processors.html to figure out what generation your CPU is.)
See [The Intel Docs](https://www.intel.com/content/www/us/en/support/articles/000005505/processors.html) to figure out what generation your CPU is.
:::
@@ -379,7 +383,7 @@ Make sure to follow the [Rockchip specific installation instructions](/frigate/i
### Configuration
Add one of the following FFmpeg presets to your `config.yaml` to enable hardware video processing:
Add one of the following FFmpeg presets to your `config.yml` to enable hardware video processing:
```yaml
# if you try to decode a h264 encoded stream

View File

@@ -11,11 +11,21 @@ Frigate intelligently uses three different streaming technologies to display you
The jsmpeg live view will use more browser and client GPU resources. Using go2rtc is highly recommended and will provide a superior experience.
| Source | Latency | Frame Rate | Resolution | Audio | Requires go2rtc | Other Limitations |
| ------ | ------- | ------------------------------------- | ---------- | ---------------------------- | --------------- | ------------------------------------------------------------------------------------ |
| jsmpeg | low | same as `detect -> fps`, capped at 10 | 720p | no | no | resolution is configurable, but go2rtc is recommended if you want higher resolutions |
| mse | low | native | native | yes (depends on audio codec) | yes | iPhone requires iOS 17.1+, Firefox is h.264 only |
| webrtc | lowest | native | native | yes (depends on audio codec) | yes | requires extra config, doesn't support h.265 |
| Source | Frame Rate | Resolution | Audio | Requires go2rtc | Notes |
| ------ | ------------------------------------- | ---------- | ---------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| jsmpeg | same as `detect -> fps`, capped at 10 | 720p | no | no | Resolution is configurable, but go2rtc is recommended if you want higher resolutions and better frame rates. jsmpeg is Frigate's default without go2rtc configured. |
| mse | native | native | yes (depends on audio codec) | yes | iPhone requires iOS 17.1+, Firefox is h.264 only. This is Frigate's default when go2rtc is configured. |
| webrtc | native | native | yes (depends on audio codec) | yes | Requires extra configuration, doesn't support h.265. Frigate attempts to use WebRTC when MSE fails or when using a camera's two-way talk feature. |
### Camera Settings Recommendations
If you are using go2rtc, you should adjust the following settings in your camera's firmware for the best experience with Live view:
- Video codec: **H.264** - provides the most compatible video codec with all Live view technologies and browsers. Avoid any kind of "smart codec" or "+" codec like _H.264+_ or _H.265+_. as these non-standard codecs remove keyframes (see below).
- Audio codec: **AAC** - provides the most compatible audio codec with all Live view technologies and browsers that support audio.
- I-frame interval (sometimes called the keyframe interval, the interframe space, or the GOP length): match your camera's frame rate, or choose "1x" (for interframe space on Reolink cameras). For example, if your stream outputs 20fps, your i-frame interval should be 20 (or 1x on Reolink). Values higher than the frame rate will cause the stream to take longer to begin playback. See [this page](https://gardinal.net/understanding-the-keyframe-interval/) for more on keyframes.
The default video and audio codec on your camera may not always be compatible with your browser, which is why setting them to H.264 and AAC is recommended. See the [go2rtc docs](https://github.com/AlexxIT/go2rtc?tab=readme-ov-file#codecs-madness) for codec support information.
### Audio Support
@@ -32,6 +42,15 @@ go2rtc:
- "ffmpeg:http_cam#audio=opus" # <- copy of the stream which transcodes audio to the missing codec (usually will be opus)
```
If your camera does not have audio and you are having problems with Live view, you should have go2rtc send video only:
```yaml
go2rtc:
streams:
no_audio_camera:
- ffmpeg:rtsp://192.168.1.5:554/live0#video=copy
```
### Setting Stream For Live UI
There may be some cameras that you would prefer to use the sub stream for live view, but the main stream for recording. This can be done via `live -> stream_name`.

View File

@@ -5,6 +5,8 @@ title: Object Detectors
# Supported Hardware
:::info
Frigate supports multiple different detectors that work on different types of hardware:
**Most Hardware**
@@ -26,37 +28,14 @@ Frigate supports multiple different detectors that work on different types of ha
**Rockchip**
- [RKNN](#rockchip-platform): RKNN models can run on Rockchip devices with included NPUs.
# Officially Supported Detectors
Frigate provides the following builtin detector types: `cpu`, `edgetpu`, `openvino`, `tensorrt`, `rknn`, and `hailo8l`. By default, Frigate will use a single CPU detector. Other detectors may require additional configuration as described below. When using multiple detectors they will run in dedicated processes, but pull from a common queue of detection requests from across all cameras.
## CPU Detector (not recommended)
The CPU detector type runs a TensorFlow Lite model utilizing the CPU without hardware acceleration. It is recommended to use a hardware accelerated detector type instead for better performance. To configure a CPU based detector, set the `"type"` attribute to `"cpu"`.
:::tip
If you do not have GPU or Edge TPU hardware, using the [OpenVINO Detector](#openvino-detector) is often more efficient than using the CPU detector.
**For Testing**
- [CPU Detector (not recommended for actual use](#cpu-detector-not-recommended): Use a CPU to run tflite model, this is not recommended and in most cases OpenVINO can be used in CPU mode with better results.
:::
The number of threads used by the interpreter can be specified using the `"num_threads"` attribute, and defaults to `3.`
# Officially Supported Detectors
A TensorFlow Lite model is provided in the container at `/cpu_model.tflite` and is used by this detector type by default. To provide your own model, bind mount the file into the container and provide the path with `model.path`.
```yaml
detectors:
cpu1:
type: cpu
num_threads: 3
model:
path: "/custom_model.tflite"
cpu2:
type: cpu
num_threads: 3
```
When using CPU detectors, you can add one CPU detector per camera. Adding more detectors than the number of cameras should not improve performance.
Frigate provides the following builtin detector types: `cpu`, `edgetpu`, `hailo8l`, `onnx`, `openvino`, `rknn`, `rocm`, and `tensorrt`. By default, Frigate will use a single CPU detector. Other detectors may require additional configuration as described below. When using multiple detectors they will run in dedicated processes, but pull from a common queue of detection requests from across all cameras.
## Edge TPU Detector
@@ -188,7 +167,7 @@ This detector also supports YOLOX. Frigate does not come with any YOLOX models p
#### YOLO-NAS
[YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md) models are supported, but not included by default. You can build and download a compatible model with pre-trained weights using [this notebook](https://github.com/frigate/blob/dev/notebooks/YOLO_NAS_Pretrained_Export.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/blakeblackshear/frigate/blob/dev/notebooks/YOLO_NAS_Pretrained_Export.ipynb).
[YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md) models are supported, but not included by default. You can build and download a compatible model with pre-trained weights using [this notebook](https://github.com/blakeblackshear/frigate/blob/dev/notebooks/YOLO_NAS_Pretrained_Export.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/blakeblackshear/frigate/blob/dev/notebooks/YOLO_NAS_Pretrained_Export.ipynb).
:::warning
@@ -418,7 +397,7 @@ After placing the downloaded onnx model in your config folder, you can use the f
```yaml
detectors:
onnx:
rocm:
type: rocm
model:
@@ -484,6 +463,34 @@ model:
Note that the labelmap uses a subset of the complete COCO label set that has only 80 objects.
## CPU Detector (not recommended)
The CPU detector type runs a TensorFlow Lite model utilizing the CPU without hardware acceleration. It is recommended to use a hardware accelerated detector type instead for better performance. To configure a CPU based detector, set the `"type"` attribute to `"cpu"`.
:::danger
The CPU detector is not recommended for general use. If you do not have GPU or Edge TPU hardware, using the [OpenVINO Detector](#openvino-detector) in CPU mode is often more efficient than using the CPU detector.
:::
The number of threads used by the interpreter can be specified using the `"num_threads"` attribute, and defaults to `3.`
A TensorFlow Lite model is provided in the container at `/cpu_model.tflite` and is used by this detector type by default. To provide your own model, bind mount the file into the container and provide the path with `model.path`.
```yaml
detectors:
cpu1:
type: cpu
num_threads: 3
model:
path: "/custom_model.tflite"
cpu2:
type: cpu
num_threads: 3
```
When using CPU detectors, you can add one CPU detector per camera. Adding more detectors than the number of cameras should not improve performance.
## Deepstack / CodeProject.AI Server Detector
The Deepstack / CodeProject.AI Server detector for Frigate allows you to integrate Deepstack and CodeProject.AI object detection capabilities into Frigate. CodeProject.AI and DeepStack are open-source AI platforms that can be run on various devices such as the Raspberry Pi, Nvidia Jetson, and other compatible hardware. It is important to note that the integration is performed over the network, so the inference times may not be as fast as native Frigate detectors, but it still provides an efficient and reliable solution for object detection and tracking.
@@ -597,6 +604,8 @@ $ cat /sys/kernel/debug/rknpu/load
This detector is available for use with Hailo-8 AI Acceleration Module.
See the [installation docs](../frigate/installation.md#hailo-8l) for information on configuring the hailo8.
### Configuration
```yaml

View File

@@ -0,0 +1,24 @@
---
id: pwa
title: Installing Frigate App
---
Frigate supports being installed as a [Progressive Web App](https://web.dev/explore/progressive-web-apps) on Desktop, Android, and iOS.
This adds features including the ability to deep link directly into the app.
## Requirements
In order to install Frigate as a PWA, the following requirements must be met:
- Frigate must be accessed via a secure context (localhost, secure https, etc.)
- On Android, Firefox, Chrome, Edge, Opera, and Samsung Internet Browser all support installing PWAs.
- On iOS 16.4 and later, PWAs can be installed from the Share menu in Safari, Chrome, Edge, Firefox, and Orion.
## Installation
Installation varies slightly based on the device that is being used:
- Desktop: Use the install button typically found in right edge of the address bar
- Android: Use the `Install as App` button in the more options menu
- iOS: Use the `Add to Homescreen` button in the share menu

View File

@@ -154,7 +154,7 @@ Footage can be exported from Frigate by right-clicking (desktop) or long pressin
### Time-lapse export
Time lapse exporting is available only via the [HTTP API](../integrations/api.md#post-apiexportcamerastartstart-timestampendend-timestamp).
Time lapse exporting is available only via the [HTTP API](../integrations/api/export-recording-export-camera-name-start-start-time-end-end-time-post.api.mdx).
When exporting a time-lapse the default speed-up is 25x with 30 FPS. This means that every 25 seconds of (real-time) recording is condensed into 1 second of time-lapse video (always without audio) with a smoothness of 30 FPS.

View File

@@ -138,6 +138,16 @@ model:
# Optional: Label name modifications. These are merged into the standard labelmap.
labelmap:
2: vehicle
# Optional: Map of object labels to their attribute labels (default: depends on model)
attributes_map:
person:
- amazon
- face
car:
- amazon
- fedex
- license_plate
- ups
# Optional: Audio Events Configuration
# NOTE: Can be overridden at the camera level
@@ -324,6 +334,9 @@ review:
- car
- person
# Optional: required zones for an object to be marked as an alert (default: none)
# NOTE: when settings required zones globally, this zone must exist on all cameras
# or the config will be considered invalid. In that case the required_zones
# should be configured at the camera level.
required_zones:
- driveway
# Optional: detections configuration
@@ -333,12 +346,20 @@ review:
- car
- person
# Optional: required zones for an object to be marked as a detection (default: none)
# NOTE: when settings required zones globally, this zone must exist on all cameras
# or the config will be considered invalid. In that case the required_zones
# should be configured at the camera level.
required_zones:
- driveway
# Optional: Motion configuration
# NOTE: Can be overridden at the camera level
motion:
# Optional: enables detection for the camera (default: True)
# NOTE: Motion detection is required for object detection,
# setting this to False and leaving detect enabled
# will result in an error on startup.
enabled: False
# Optional: The threshold passed to cv2.threshold to determine if a pixel is different enough to be counted as motion. (default: shown below)
# Increasing this value will make motion detection less sensitive and decreasing it will make motion detection more sensitive.
# The value should be between 1 and 255.
@@ -716,6 +737,8 @@ cameras:
genai:
# Optional: Enable AI description generation (default: shown below)
enabled: False
# Optional: Use the object snapshot instead of thumbnails for description generation (default: shown below)
use_snapshot: False
# Optional: The default prompt for generating descriptions. Can use replacement
# variables like "label", "sub_label", "camera" to make more dynamic. (default: shown below)
prompt: "Describe the {label} in the sequence of images with as much detail as possible. Do not describe the background."
@@ -723,6 +746,12 @@ cameras:
# Format: {label}: {prompt}
object_prompts:
person: "My special person prompt."
# Optional: objects to generate descriptions for (default: all objects that are tracked)
objects:
- person
- cat
# Optional: Restrict generation to objects that entered any of the listed zones (default: none, all zones qualify)
required_zones: []
# Optional
ui:
@@ -786,7 +815,7 @@ camera_groups:
- side_cam
- front_doorbell_cam
# Required: icon used for group
icon: car
icon: LuCar
# Required: index of this group
order: 0
```

View File

@@ -41,8 +41,6 @@ review:
By default all detections that do not qualify as an alert qualify as a detection. However, detections can further be filtered to only include certain labels or certain zones.
By default a review item will only be marked as an alert if a person or car is detected. This can be configured to include any object or audio label using the following config:
```yaml
# can be overridden at the camera level
review:

View File

@@ -5,7 +5,7 @@ title: Using Semantic Search
Semantic Search in Frigate allows you to find tracked objects within your review items using either the image itself, a user-defined text description, or an automatically generated one. This feature works by creating _embeddings_ — numerical vector representations — for both the images and text descriptions of your tracked objects. By comparing these embeddings, Frigate assesses their similarities to deliver relevant search results.
Frigate has support for two models to create embeddings, both of which run locally: [OpenAI CLIP](https://openai.com/research/clip) and [all-MiniLM-L6-v2](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2). Embeddings are then saved to a local instance of [ChromaDB](https://trychroma.com).
Frigate has support for two models to create embeddings, both of which run locally: [OpenAI CLIP](https://openai.com/research/clip) and [all-MiniLM-L6-v2](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2). Embeddings are then saved to Frigate's database.
Semantic Search is accessed via the _Explore_ view in the Frigate UI.
@@ -29,7 +29,7 @@ If you are enabling the Search feature for the first time, be advised that Friga
### OpenAI CLIP
This model is able to embed both images and text into the same vector space, which allows `image -> image` and `text -> image` similarity searches. Frigate uses this model on tracked objects to encode the thumbnail image and store it in Chroma. When searching for tracked objects via text in the search box, Frigate will perform a `text -> image` similarity search against this embedding. When clicking "Find Similar" in the tracked object detail pane, Frigate will perform an `image -> image` similarity search to retrieve the closest matching thumbnails.
This model is able to embed both images and text into the same vector space, which allows `image -> image` and `text -> image` similarity searches. Frigate uses this model on tracked objects to encode the thumbnail image and store it in the database. When searching for tracked objects via text in the search box, Frigate will perform a `text -> image` similarity search against this embedding. When clicking "Find Similar" in the tracked object detail pane, Frigate will perform an `image -> image` similarity search to retrieve the closest matching thumbnails.
### all-MiniLM-L6-v2

View File

@@ -3,7 +3,7 @@ id: snapshots
title: Snapshots
---
Frigate can save a snapshot image to `/media/frigate/clips` for each object that is detected named as `<camera>-<id>.jpg`. They are also accessible [via the api](../integrations/api.md#get-apieventsidsnapshotjpg)
Frigate can save a snapshot image to `/media/frigate/clips` for each object that is detected named as `<camera>-<id>.jpg`. They are also accessible [via the api](../integrations/api/event-snapshot-events-event-id-snapshot-jpg-get.api.mdx)
For users with Frigate+ enabled, snapshots are accessible in the UI in the Frigate+ pane to allow for quick submission to the Frigate+ service.

View File

@@ -193,7 +193,7 @@ npm run test
#### 1. Installation
```console
npm install
cd docs && npm install
```
#### 2. Local Development

View File

@@ -69,6 +69,7 @@ Inference speeds vary greatly depending on the CPU, GPU, or VPU used, some known
| Intel i5 7500 | ~ 15 ms | Inference speeds on CPU were ~ 260 ms |
| Intel i5 1135G7 | 10 - 15 ms | |
| Intel i5 12600K | ~ 15 ms | Inference speeds on CPU were ~ 35 ms |
| Intel Arc A750 | ~ 4 ms | |
### TensorRT - Nvidia GPU

View File

@@ -112,8 +112,8 @@ For other installations, follow these steps for installation:
1. Install the driver from the [Hailo GitHub repository](https://github.com/hailo-ai/hailort-drivers). A convenient script for Linux is available to clone the repository, build the driver, and install it.
2. Copy or download [this script](https://github.com/blakeblackshear/frigate/blob/41c9b13d2fffce508b32dfc971fa529b49295fbd/docker/hailo8l/user_installation.sh).
3. Ensure it has execution permissions with `sudo chmod +x install_hailo8l_driver.sh`
4. Run the script with `./install_hailo8l_driver.sh`
3. Ensure it has execution permissions with `sudo chmod +x user_installation.sh`
4. Run the script with `./user_installation.sh`
#### Setup

View File

@@ -13,7 +13,7 @@ Use of the bundled go2rtc is optional. You can still configure FFmpeg to connect
# Setup a go2rtc stream
First, you will want to configure go2rtc to connect to your camera stream by adding the stream you want to use for live view in your Frigate config file. If you set the stream name under go2rtc to match the name of your camera, it will automatically be mapped and you will get additional live view options for the camera. Avoid changing any other parts of your config at this step. Note that go2rtc supports [many different stream types](https://github.com/AlexxIT/go2rtc/tree/v1.9.4#module-streams), not just rtsp.
First, you will want to configure go2rtc to connect to your camera stream by adding the stream you want to use for live view in your Frigate config file. For the best experience, you should set the stream name under go2rtc to match the name of your camera so that Frigate will automatically map it and be able to use better live view options for the camera. Avoid changing any other parts of your config at this step. Note that go2rtc supports [many different stream types](https://github.com/AlexxIT/go2rtc/tree/v1.9.4#module-streams), not just rtsp.
```yaml
go2rtc:
@@ -22,7 +22,7 @@ go2rtc:
- rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2
```
The easiest live view to get working is MSE. After adding this to the config, restart Frigate and try to watch the live stream by selecting MSE in the dropdown after clicking on the camera.
After adding this to the config, restart Frigate and try to watch the live stream for a single camera by clicking on it from the dashboard. It should look much clearer and more fluent than the original jsmpeg stream.
### What if my video doesn't play?
@@ -46,7 +46,7 @@ The easiest live view to get working is MSE. After adding this to the config, re
streams:
back:
- rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2
- "ffmpeg:back#video=h264"
- "ffmpeg:back#video=h264#hardware"
```
- Switch to FFmpeg if needed:
@@ -58,9 +58,8 @@ The easiest live view to get working is MSE. After adding this to the config, re
- ffmpeg:rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2
```
- If you can see the video but do not have audio, this is most likely because your
camera's audio stream is not AAC.
- If possible, update your camera's audio settings to AAC.
- If you can see the video but do not have audio, this is most likely because your camera's audio stream codec is not AAC.
- If possible, update your camera's audio settings to AAC in your camera's firmware.
- If your cameras do not support AAC audio, you will need to tell go2rtc to re-encode the audio to AAC on demand if you want audio. This will use additional CPU and add some latency. To add AAC audio on demand, you can update your go2rtc config as follows:
```yaml
go2rtc:
@@ -77,7 +76,7 @@ camera's audio stream is not AAC.
streams:
back:
- rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2
- "ffmpeg:back#video=h264#audio=aac"
- "ffmpeg:back#video=h264#audio=aac#hardware"
```
When using the ffmpeg module, you would add AAC audio like this:
@@ -86,7 +85,7 @@ camera's audio stream is not AAC.
go2rtc:
streams:
back:
- "ffmpeg:rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2#video=copy#audio=copy#audio=aac"
- "ffmpeg:rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2#video=copy#audio=copy#audio=aac#hardware"
```
:::warning
@@ -102,4 +101,4 @@ section.
## Next steps
1. If the stream you added to go2rtc is also used by Frigate for the `record` or `detect` role, you can migrate your config to pull from the RTSP restream to reduce the number of connections to your camera as shown [here](/configuration/restream#reduce-connections-to-camera).
1. You may also prefer to [setup WebRTC](/configuration/live#webrtc-extra-configuration) for slightly lower latency than MSE. Note that WebRTC only supports h264 and specific audio formats.
2. You may also prefer to [setup WebRTC](/configuration/live#webrtc-extra-configuration) for slightly lower latency than MSE. Note that WebRTC only supports h264 and specific audio formats and may require opening ports on your router.

View File

@@ -3,25 +3,38 @@ id: reverse_proxy
title: Setting up a reverse proxy
---
This guide outlines the basic configuration steps needed to expose your Frigate UI to the internet.
A common way of accomplishing this is to use a reverse proxy webserver between your router and your Frigate instance.
A reverse proxy accepts HTTP requests from the public internet and redirects them transparently to internal webserver(s) on your network.
This guide outlines the basic configuration steps needed to set up a reverse proxy in front of your Frigate instance.
The suggested steps are:
A reverse proxy is typically needed if you want to set up Frigate on a custom URL, on a subdomain, or on a host serving multiple sites. It could also be used to set up your own authentication provider or for more advanced HTTP routing.
- **Configure** a 'proxy' HTTP webserver (such as [Apache2](https://httpd.apache.org/docs/current/) or [NPM](https://github.com/NginxProxyManager/nginx-proxy-manager)) and only expose ports 80/443 from this webserver to the internet
- **Encrypt** content from the proxy webserver by installing SSL (such as with [Let's Encrypt](https://letsencrypt.org/)). Note that SSL is then not required on your Frigate webserver as the proxy encrypts all requests for you
- **Restrict** access to your Frigate instance at the proxy using, for example, password authentication
Before setting up a reverse proxy, check if any of the built-in functionality in Frigate suits your needs:
|Topic|Docs|
|-|-|
|TLS|Please see the `tls` [configuration option](../configuration/tls.md)|
|Authentication|Please see the [authentication](../configuration/authentication.md) documentation|
|IPv6|[Enabling IPv6](../configuration/advanced.md#enabling-ipv6)
**Note about TLS**
When using a reverse proxy, the TLS session is usually terminated at the proxy, sending the internal request over plain HTTP. If this is the desired behavior, TLS must first be disabled in Frigate, or you will encounter an HTTP 400 error: "The plain HTTP request was sent to HTTPS port."
To disable TLS, set the following in your Frigate configuration:
```yml
tls:
enabled: false
```
:::warning
A reverse proxy can be used to secure access to an internal webserver but the user will be entirely reliant
on the steps they have taken. You must ensure you are following security best practices.
This page does not attempt to outline the specific steps needed to secure your internal website.
A reverse proxy can be used to secure access to an internal web server, but the user will be entirely reliant on the steps they have taken. You must ensure you are following security best practices.
This page does not attempt to outline the specific steps needed to secure your internal website.
Please use your own knowledge to assess and vet the reverse proxy software before you install anything on your system.
:::
There are several technologies available to implement reverse proxies. This document currently suggests one, using Apache2,
and the community is invited to document others through a contribution to this page.
## Proxies
There are many solutions available to implement reverse proxies and the community is invited to help out documenting others through a contribution to this page.
* [Apache2](#apache2-reverse-proxy)
* [Nginx](#nginx-reverse-proxy)
* [Traefik](#traefik-reverse-proxy)
## Apache2 Reverse Proxy
@@ -141,3 +154,26 @@ The settings below enabled connection upgrade, sets up logging (optional) and pr
}
```
## Traefik Reverse Proxy
This example shows how to add a `label` to the Frigate Docker compose file, enabling Traefik to automatically discover your Frigate instance.
Before using the example below, you must first set up Traefik with the [Docker provider](https://doc.traefik.io/traefik/providers/docker/)
```yml
services:
frigate:
container_name: frigate
image: ghcr.io/blakeblackshear/frigate:stable
...
...
labels:
- "traefik.enable=true"
- "traefik.http.services.frigate.loadbalancer.server.port=8971"
- "traefik.http.routers.frigate.rule=Host(`traefik.example.com`)"
```
The above configuration will create a "service" in Traefik, automatically adding your container's IP on port 8971 as a backend.
It will also add a router, routing requests to "traefik.example.com" to your local container.
Note that with this approach, you don't need to expose any ports for the Frigate instance since all traffic will be routed over the internal Docker network.

View File

@@ -1,534 +0,0 @@
---
id: api
title: HTTP API
---
A web server is available on port 5000 with the following endpoints.
## Management & Information
### `GET /api/config`
A json representation of your configuration
### `POST /api/restart`
Restarts Frigate process.
### `GET /api/stats`
Contains some granular debug info that can be used for sensors in Home Assistant.
Sample response:
```json
{
/* Per Camera Stats */
"cameras": {
"back": {
/***************
* Frames per second being consumed from your camera. If this is higher
* than it is supposed to be, you should set -r FPS in your input_args.
* camera_fps = process_fps + skipped_fps
***************/
"camera_fps": 5.0,
/***************
* Number of times detection is run per second. This can be higher than
* your camera FPS because Frigate often looks at the same frame multiple times
* or in multiple locations
***************/
"detection_fps": 1.5,
/***************
* PID for the ffmpeg process that consumes this camera
***************/
"capture_pid": 27,
/***************
* PID for the process that runs detection for this camera
***************/
"pid": 34,
/***************
* Frames per second being processed by Frigate.
***************/
"process_fps": 5.1,
/***************
* Frames per second skip for processing by Frigate.
***************/
"skipped_fps": 0.0
}
},
/***************
* Sum of detection_fps across all cameras and detectors.
* This should be the sum of all detection_fps values from cameras.
***************/
"detection_fps": 5.0,
/* Detectors Stats */
"detectors": {
"coral": {
/***************
* Timestamp when object detection started. If this value stays non-zero and constant
* for a long time, that means the detection process is stuck.
***************/
"detection_start": 0.0,
/***************
* Time spent running object detection in milliseconds.
***************/
"inference_speed": 10.48,
/***************
* PID for the shared process that runs object detection on the Coral.
***************/
"pid": 25321
}
},
"service": {
/* Uptime in seconds */
"uptime": 10,
"version": "0.10.1-8883709",
"latest_version": "0.10.1",
/* Storage data in MB for important locations */
"storage": {
"/media/frigate/clips": {
"total": 1000,
"used": 700,
"free": 300,
"mnt_type": "ext4"
},
"/media/frigate/recordings": {
"total": 1000,
"used": 700,
"free": 300,
"mnt_type": "ext4"
},
"/tmp/cache": {
"total": 256,
"used": 100,
"free": 156,
"mnt_type": "tmpfs"
},
"/dev/shm": {
"total": 256,
"used": 100,
"free": 156,
"mnt_type": "tmpfs"
}
}
},
"cpu_usages": {
"pid": {
"cmdline": "ffmpeg...",
"cpu": "5.0",
"cpu_average": "3.0",
"mem": "0.5"
}
},
"gpu_usages": {
"gpu-type": {
"gpu": "17%",
"mem": "18%"
}
}
}
```
### `GET /api/version`
Version info
### `GET /api/ffprobe`
Get ffprobe output for camera feed paths.
| param | Type | Description |
| ------- | ------ | ---------------------------------- |
| `paths` | string | `,` separated list of camera paths |
### `GET /api/<camera_name>/ptz/info`
Get PTZ info for the camera.
## Camera Media
### `GET /api/<camera_name>`
An mjpeg stream for debugging. Keep in mind the mjpeg endpoint is for debugging only and will put additional load on the system when in use.
Accepts the following query string parameters:
| param | Type | Description |
| ----------- | ---- | ------------------------------------------------------------------ |
| `fps` | int | Frame rate |
| `h` | int | Height in pixels |
| `bbox` | int | Show bounding boxes for detected objects (0 or 1) |
| `timestamp` | int | Print the timestamp in the upper left (0 or 1) |
| `zones` | int | Draw the zones on the image (0 or 1) |
| `mask` | int | Overlay the mask on the image (0 or 1) |
| `motion` | int | Draw blue boxes for areas with detected motion (0 or 1) |
| `regions` | int | Draw green boxes for areas where object detection was run (0 or 1) |
You can access a higher resolution mjpeg stream by appending `h=height-in-pixels` to the endpoint. For example `/api/back?h=1080`. You can also increase the FPS by appending `fps=frame-rate` to the URL such as `/api/back?fps=10` or both with `?fps=10&h=1000`.
### `GET /api/<camera_name>/latest.jpg[?h=300]`
The most recent frame that Frigate has finished processing. It is a full resolution image by default.
Accepts the following query string parameters:
| param | Type | Description |
| ----------- | ---- | ------------------------------------------------------------------ |
| `h` | int | Height in pixels |
| `bbox` | int | Show bounding boxes for detected objects (0 or 1) |
| `timestamp` | int | Print the timestamp in the upper left (0 or 1) |
| `zones` | int | Draw the zones on the image (0 or 1) |
| `mask` | int | Overlay the mask on the image (0 or 1) |
| `motion` | int | Draw blue boxes for areas with detected motion (0 or 1) |
| `regions` | int | Draw green boxes for areas where object detection was run (0 or 1) |
| `quality` | int | Jpeg encoding quality (0-100). Defaults to 70. |
Example parameters:
- `h=300`: resizes the image to 300 pixels tall
### `GET /api/<camera_name>/<label>/thumbnail.jpg`
Returns the thumbnail from the latest tracked object for the given camera and label combo. Using `any` as the label will return the latest thumbnail regardless of type.
### `GET /api/<camera_name>/<label>/clip.mp4`
Returns the clip from the latest tracked object for the given camera and label combo. Using `any` as the label will return the latest clip regardless of type.
### `GET /api/<camera_name>/<label>/snapshot.jpg`
Returns the snapshot image from the latest tracked object for the given camera and label combo. Using `any` as the label will return the latest thumbnail regardless of type.
### `GET /api/<camera_name>/grid.jpg`
Returns the latest camera image with the regions grid overlaid.
| param | Type | Description |
| ------------ | ----- | ------------------------------------------------------------------------------------------ |
| `color` | str | The color of the grid (red,green,blue,black,white). Defaults to "green". |
| `font_scale` | float | Font scale. Can be used to increase font size on high resolution cameras. Defaults to 0.5. |
### `GET /clips/<camera>-<id>.jpg`
JPG snapshot for the given camera and event id.
## Events
### `GET /api/events`
Events from the database. Accepts the following query string parameters:
| param | Type | Description |
| -------------------- | ----- | ----------------------------------------------------- |
| `before` | int | Epoch time |
| `after` | int | Epoch time |
| `cameras` | str | , separated list of cameras |
| `labels` | str | , separated list of labels |
| `zones` | str | , separated list of zones |
| `limit` | int | Limit the number of events returned |
| `has_snapshot` | int | Filter to events that have snapshots (0 or 1) |
| `has_clip` | int | Filter to events that have clips (0 or 1) |
| `include_thumbnails` | int | Include thumbnails in the response (0 or 1) |
| `in_progress` | int | Limit to events in progress (0 or 1) |
| `time_range` | str | Time range in format after,before (00:00,24:00) |
| `timezone` | str | Timezone to use for time range |
| `min_score` | float | Minimum score of the event |
| `max_score` | float | Maximum score of the event |
| `is_submitted` | int | Filter events that are submitted to Frigate+ (0 or 1) |
| `min_length` | float | Minimum length of the event |
| `max_length` | float | Maximum length of the event |
### `GET /api/events/summary`
Returns summary data for events in the database. Used by the Home Assistant integration.
### `GET /api/events/<id>`
Returns data for a single event.
### `DELETE /api/events/<id>`
Permanently deletes the event along with any clips/snapshots.
### `POST /api/events/<id>/retain`
Sets retain to true for the event id.
### `POST /api/events/<id>/plus`
Submits the snapshot of the event to Frigate+ for labeling.
| param | Type | Description |
| -------------------- | ---- | ---------------------------------- |
| `include_annotation` | int | Submit annotation to Frigate+ too. |
### `PUT /api/events/<id>/false_positive`
Submits the snapshot of the event to Frigate+ for labeling and adds the detection as a false positive.
### `DELETE /api/events/<id>/retain`
Sets retain to false for the event id (event may be deleted quickly after removing).
### `POST /api/events/<id>/sub_label`
Set a sub label for an event. For example to update `person` -> `person's name` if they were recognized with facial recognition.
Sub labels must be 100 characters or shorter.
```json
{
"subLabel": "some_string",
"subLabelScore": 0.79
}
```
### `GET /api/events/<id>/thumbnail.jpg`
Returns a thumbnail for the event id optimized for notifications. Works while the event is in progress and after completion. Passing `?format=android` will convert the thumbnail to 2:1 aspect ratio.
### `GET /api/events/<id>/clip.mp4`
Returns the clip for the event id. Works after the event has ended.
### `GET /api/events/<id>/snapshot-clean.png`
Returns the clean snapshot image for the event id. Only works if `snapshots` and `clean_copy` are enabled in the config.
| param | Type | Description |
| ---------- | ---- | ------------------ |
| `download` | bool | Download the image |
### `GET /api/events/<id>/snapshot.jpg`
Returns the snapshot image for the event id. Works while the event is in progress and after completion.
Accepts the following query string parameters, but they are only applied when an event is in progress. After the event is completed, the saved snapshot is returned from disk without modification:
| param | Type | Description |
| ----------- | ---- | ------------------------------------------------- |
| `h` | int | Height in pixels |
| `bbox` | int | Show bounding boxes for detected objects (0 or 1) |
| `timestamp` | int | Print the timestamp in the upper left (0 or 1) |
| `crop` | int | Crop the snapshot to the (0 or 1) |
| `quality` | int | Jpeg encoding quality (0-100). Defaults to 70. |
| `download` | bool | Download the image |
### `POST /api/events/<camera_name>/<label>/create`
Create a manual event with a given `label` (ex: doorbell press) to capture a specific event besides an object being detected.
:::warning
Recording retention config still applies to manual events, if frigate is configured with `mode: motion` then the manual event will only keep recording segments when motion occurred.
:::
**Optional Body:**
```json
{
"sub_label": "some_string", // add sub label to event
"duration": 30, // predetermined length of event (default: 30 seconds) or can be to null for indeterminate length event
"include_recording": true, // whether the event should save recordings along with the snapshot that is taken
"draw": {
// optional annotations that will be drawn on the snapshot
"boxes": [
{
"box": [0.5, 0.5, 0.25, 0.25], // box consists of x, y, width, height which are on a scale between 0 - 1
"color": [255, 0, 0], // color of the box, default is red
"score": 100 // optional score associated with the box
}
]
}
}
```
**Success Response:**
```json
{
"event_id": "1682970645.13116-1ug7ns",
"message": "Successfully created event.",
"success": true
}
```
### `PUT /api/events/<event_id>/end`
End a specific manual event without a predetermined length.
### `GET /api/events/<id>/preview.gif`
Gif covering the first 20 seconds of a specific event.
## Previews
Previews are low res / fps videos that are quickly scrubbable and can be used for notifications or time-lapses.
### `GET /api/preview/<camera>/start/<start-timestamp>/end/<end-timestamp>`
Metadata about previews for this time range.
### `GET /api/preview/<year>-<month>/<day>/<hour>/<camera>/<timezone>`
Metadata about previews for this hour
### `GET /api/preview/<camera>/start/<start-timestamp>/end/<end-timestamp>`
List of frames in the preview cache for the time range. Previews are only kept in the cache until they are combined into an mp4 at the end of the hour.
### `GET /api/preview/<file_name>/thumbnail.jpg`
Specific preview frame from preview cache.
### `GET /<camera>/start/<start-timestamp>/end/<end-timestamp>/preview`
Looping image made from preview video / frames during this time range.
| param | Type | Description |
| -------- | ---- | -------------------------------- |
| `format` | str | Format of preview [`gif`, `mp4`] |
## Recordings
### `GET /vod/<year>-<month>/<day>/<hour>/<camera>/master.m3u8`
HTTP Live Streaming Video on Demand URL for the specified hour and camera. Can be viewed in an application like VLC.
### `GET /vod/event/<event-id>/index.m3u8`
HTTP Live Streaming Video on Demand URL for the specified event. Can be viewed in an application like VLC.
### `GET /vod/<camera>/start/<start-timestamp>/end/<end-timestamp>/index.m3u8`
HTTP Live Streaming Video on Demand URL for the camera with the specified time range. Can be viewed in an application like VLC.
### `POST /api/export/<camera>/start/<start-timestamp>/end/<end-timestamp>`
Export recordings from `start-timestamp` to `end-timestamp` for `camera` as a single mp4 file. These recordings will be exported to the `/media/frigate/exports` folder.
It is also possible to export this recording as a time-lapse.
**Optional Body:**
```json
{
"playback": "realtime" // playback factor: realtime or timelapse_25x
}
```
### `DELETE /api/export/<export_name>`
Delete an export from disk.
### `PATCH /api/export/<export_name_current>/<export_name_new>`
Renames an export.
### `GET /api/<camera_name>/recordings/summary`
Hourly summary of recordings data for a camera.
### `GET /api/<camera_name>/recordings`
Get recording segment details for the given timestamp range.
| param | Type | Description |
| -------- | ---- | ------------------------------------- |
| `after` | int | Unix timestamp for beginning of range |
| `before` | int | Unix timestamp for end of range |
### `GET /api/<camera_name>/recordings/<frame_time>/snapshot.png`
Returns the snapshot image from the specific point in that cameras recordings.
## Reviews
### `GET /api/review`
Reviews from the database. Accepts the following query string parameters:
| param | Type | Description |
| ---------- | ---- | -------------------------------------------------------------- |
| `before` | int | Epoch time |
| `after` | int | Epoch time |
| `cameras` | str | , separated list of cameras |
| `labels` | str | , separated list of labels |
| `zones` | str | , separated list of zones |
| `reviewed` | int | Include items that have been reviewed (0 or 1) |
| `limit` | int | Limit the number of events returned |
| `severity` | str | Limit items to severity (alert, detection, significant_motion) |
### `GET /api/review/<id>`
Get review with `id` from the database.
### `GET /api/review/summary`
Summary of reviews for the last 30 days. Accepts the following query string parameters:
| param | Type | Description |
| ---------- | ---- | --------------------------- |
| `cameras` | str | , separated list of cameras |
| `labels` | str | , separated list of labels |
| `timezone` | str | Timezone name |
### `POST /api/reviews/viewed`
Mark item(s) as reviewed.
**Required Body:**
```json
{
"ids": ["123", "456"] // , separated list of review IDs
}
```
### `DELETE /api/review/<id>/viewed`
Mark an item as not reviewed.
### `POST /api/reviews/delete`
Delete review items.
**Required Body:**
```json
{
"ids": ["123", "456"] // , separated list of review IDs
}
```
### `GET /review/activity/motion`
Get the motion activity for camera(s) during a specified time period.
| param | Type | Description |
| --------- | ---- | --------------------------- |
| `before` | int | Epoch time |
| `after` | int | Epoch time |
| `cameras` | str | , separated list of cameras |
### `GET /review/activity/audio`
Get the audio activity for camera(s) during a specified time period.
| param | Type | Description |
| --------- | ---- | --------------------------- |
| `before` | int | Epoch time |
| `after` | int | Epoch time |
| `cameras` | str | , separated list of cameras |
## Timeline
### `GET /api/timeline`
Timeline of key moments of an event(s) from the database. Accepts the following query string parameters:
| param | Type | Description |
| ----------- | ---- | ----------------------------------- |
| `camera` | str | Name of camera |
| `source_id` | str | ID of tracked object |
| `limit` | int | Limit the number of events returned |

View File

@@ -25,7 +25,7 @@ Available via HACS as a default repository. To install:
- Use [HACS](https://hacs.xyz/) to install the integration:
```
Home Assistant > HACS > Integrations > "Explore & Add Integrations" > Frigate
Home Assistant > HACS > Click in the Search bar and type "Frigate" > Frigate
```
- Restart Home Assistant.
@@ -215,7 +215,7 @@ For advanced usecases, this behavior can be changed with the [RTSP URL
template](#options) option. When set, this string will override the default stream
address that is derived from the default behavior described above. This option supports
[jinja2 templates](https://jinja.palletsprojects.com/) and has the `camera` dict
variables from [Frigate API](api.md)
variables from [Frigate API](../integrations/api)
available for the template. Note that no Home Assistant state is available to the
template, only the camera dict from Frigate.

View File

@@ -11,7 +11,7 @@ These are the MQTT messages generated by Frigate. The default topic_prefix is `f
Designed to be used as an availability topic with Home Assistant. Possible message are:
"online": published when Frigate is running (on startup)
"offline": published right before Frigate stops
"offline": published after Frigate has stopped
### `frigate/restart`
@@ -147,6 +147,10 @@ Message published for each changed review item. The first message is published w
Same data available at `/api/stats` published at a configurable interval.
### `frigate/camera_activity`
Returns data about each camera, its current features, and if it is detecting motion, objects, etc. Can be triggered by publising to `frigate/onConnect`
### `frigate/notifications/set`
Topic to turn notifications on and off. Expected values are `ON` and `OFF`.

View File

@@ -23,7 +23,7 @@ In Frigate, you can use an environment variable or a docker secret named `PLUS_A
:::warning
You cannot use the `environment_vars` section of your configuration file to set this environment variable.
You cannot use the `environment_vars` section of your Frigate configuration file to set this environment variable. It must be defined as an environment variable in the docker config or HA addon config.
:::

View File

@@ -18,3 +18,7 @@ Please use your own knowledge to assess and vet them before you install anything
[Double Take](https://github.com/skrashevich/double-take) provides an unified UI and API for processing and training images for facial recognition.
It supports automatically setting the sub labels in Frigate for person objects that are detected and recognized.
This is a fork (with fixed errors and new features) of [original Double Take](https://github.com/jakowenko/double-take) project which, unfortunately, isn't being maintained by author.
## [Frigate telegram](https://github.com/OldTyT/frigate-telegram)
[Frigate telegram](https://github.com/OldTyT/frigate-telegram) makes it possible to send events from Frigate to Telegram. Events are sent as a message with a text description, video, and thumbnail.

View File

@@ -28,6 +28,18 @@ The USB coral has different IDs when it is uninitialized and initialized.
- When running Frigate in a VM, Proxmox lxc, etc. you must ensure both device IDs are mapped.
- When running HA OS you may need to run the Full Access version of the Frigate addon with the `Protected Mode` switch disabled so that the coral can be accessed.
### Synology 716+II running DSM 7.2.1-69057 Update 5
Some users have reported that this older device runs an older kernel causing issues with the coral not being detected. The following steps allowed it to be detected correctly:
1. Plug in the coral TPU in any of the USB ports on the NAS
2. Open the control panel - info screen. The coral TPU would be shown as a generic device.
3. Start the docker container with Coral TPU enabled in the config
4. The TPU would be detected but a few moments later it would disconnect.
5. While leaving the TPU device plugged in, restart the NAS using the reboot command in the UI. Do NOT unplug the NAS/power it off etc.
6. Open the control panel - info scree. The coral TPU will now be recognised as a USB Device - google inc
7. Start the frigate container. Everything should work now!
## USB Coral Detection Appears to be Stuck
The USB Coral can become stuck and need to be restarted, this can happen for a number of reasons depending on hardware and software setup. Some common reasons are:

View File

@@ -1,102 +0,0 @@
const path = require("path");
module.exports = {
title: "Frigate",
tagline: "NVR With Realtime Object Detection for IP Cameras",
url: "https://docs.frigate.video",
baseUrl: "/",
onBrokenLinks: "throw",
onBrokenMarkdownLinks: "warn",
favicon: "img/favicon.ico",
organizationName: "blakeblackshear",
projectName: "frigate",
themes: ["@docusaurus/theme-mermaid"],
markdown: {
mermaid: true,
},
themeConfig: {
algolia: {
appId: "WIURGBNBPY",
apiKey: "d02cc0a6a61178b25da550212925226b",
indexName: "frigate",
},
docs: {
sidebar: {
hideable: true,
},
},
prism: {
additionalLanguages: ["bash", "json"],
},
navbar: {
title: "Frigate",
logo: {
alt: "Frigate",
src: "img/logo.svg",
srcDark: "img/logo-dark.svg",
},
items: [
{
to: "/",
activeBasePath: "docs",
label: "Docs",
position: "left",
},
{
href: "https://frigate.video",
label: "Website",
position: "right",
},
{
href: "http://demo.frigate.video",
label: "Demo",
position: "right",
},
{
href: "https://github.com/blakeblackshear/frigate",
label: "GitHub",
position: "right",
},
],
},
footer: {
style: "dark",
links: [
{
title: "Community",
items: [
{
label: "GitHub",
href: "https://github.com/blakeblackshear/frigate",
},
{
label: "Discussions",
href: "https://github.com/blakeblackshear/frigate/discussions",
},
],
},
],
copyright: `Copyright © ${new Date().getFullYear()} Blake Blackshear`,
},
},
plugins: [path.resolve(__dirname, "plugins", "raw-loader")],
presets: [
[
"@docusaurus/preset-classic",
{
docs: {
routeBasePath: "/",
sidebarPath: require.resolve("./sidebars.js"),
// Please change this to your repo.
editUrl:
"https://github.com/blakeblackshear/frigate/edit/master/docs/",
sidebarCollapsible: false,
},
theme: {
customCss: require.resolve("./src/css/custom.css"),
},
},
],
],
};

158
docs/docusaurus.config.ts Normal file
View File

@@ -0,0 +1,158 @@
import type * as Preset from '@docusaurus/preset-classic';
import * as path from 'node:path';
import type { Config, PluginConfig } from '@docusaurus/types';
import type * as OpenApiPlugin from 'docusaurus-plugin-openapi-docs';
const config: Config = {
title: 'Frigate',
tagline: 'NVR With Realtime Object Detection for IP Cameras',
url: 'https://docs.frigate.video',
baseUrl: '/',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
favicon: 'img/favicon.ico',
organizationName: 'blakeblackshear',
projectName: 'frigate',
themes: ['@docusaurus/theme-mermaid', 'docusaurus-theme-openapi-docs'],
markdown: {
mermaid: true,
},
themeConfig: {
algolia: {
appId: 'WIURGBNBPY',
apiKey: 'd02cc0a6a61178b25da550212925226b',
indexName: 'frigate',
},
docs: {
sidebar: {
hideable: true,
},
},
prism: {
additionalLanguages: ['bash', 'json'],
},
languageTabs: [
{
highlight: 'python',
language: 'python',
logoClass: 'python',
},
{
highlight: 'javascript',
language: 'nodejs',
logoClass: 'nodejs',
},
{
highlight: 'javascript',
language: 'javascript',
logoClass: 'javascript',
},
{
highlight: 'bash',
language: 'curl',
logoClass: 'curl',
},
{
highlight: "rust",
language: "rust",
logoClass: "rust",
},
],
navbar: {
title: 'Frigate',
logo: {
alt: 'Frigate',
src: 'img/logo.svg',
srcDark: 'img/logo-dark.svg',
},
items: [
{
to: '/',
activeBasePath: 'docs',
label: 'Docs',
position: 'left',
},
{
href: 'https://frigate.video',
label: 'Website',
position: 'right',
},
{
href: 'http://demo.frigate.video',
label: 'Demo',
position: 'right',
},
{
href: 'https://github.com/blakeblackshear/frigate',
label: 'GitHub',
position: 'right',
},
],
},
footer: {
style: 'dark',
links: [
{
title: 'Community',
items: [
{
label: 'GitHub',
href: 'https://github.com/blakeblackshear/frigate',
},
{
label: 'Discussions',
href: 'https://github.com/blakeblackshear/frigate/discussions',
},
],
},
],
copyright: `Copyright © ${new Date().getFullYear()} Blake Blackshear`,
},
},
plugins: [
path.resolve(__dirname, 'plugins', 'raw-loader'),
[
'docusaurus-plugin-openapi-docs',
{
id: 'openapi',
docsPluginId: 'classic', // configured for preset-classic
config: {
frigateApi: {
specPath: 'static/frigate-api.yaml',
outputDir: 'docs/integrations/api',
sidebarOptions: {
groupPathsBy: 'tag',
categoryLinkSource: 'tag',
sidebarCollapsible: true,
sidebarCollapsed: true,
},
showSchemas: true,
} satisfies OpenApiPlugin.Options,
},
},
]
] as PluginConfig[],
presets: [
[
'classic',
{
docs: {
routeBasePath: '/',
sidebarPath: './sidebars.ts',
// Please change this to your repo.
editUrl: 'https://github.com/blakeblackshear/frigate/edit/master/docs/',
sidebarCollapsible: false,
docItemComponent: '@theme/ApiItem', // Derived from docusaurus-theme-openapi
},
theme: {
customCss: './src/css/custom.css',
},
} satisfies Preset.Options,
],
],
};
export default async function createConfig() {
return config;
}

4673
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,11 +4,14 @@
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start --host 0.0.0.0",
"build": "docusaurus build",
"start": "npm run regen-docs && docusaurus start --host 0.0.0.0",
"build": "npm run regen-docs && docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"gen-api-docs": "docusaurus gen-api-docs all",
"clear-api-docs": "docusaurus clean-api-docs all",
"regen-docs": "npm run clear-api-docs && npm run gen-api-docs",
"serve": "docusaurus serve --host 0.0.0.0",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids"
@@ -17,8 +20,11 @@
"@docusaurus/core": "^3.5.2",
"@docusaurus/preset-classic": "^3.5.2",
"@docusaurus/theme-mermaid": "^3.5.2",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"@docusaurus/plugin-content-docs": "^3.5.2",
"@mdx-js/react": "^3.0.1",
"clsx": "^2.1.1",
"docusaurus-plugin-openapi-docs": "^4.1.0",
"docusaurus-theme-openapi-docs": "^4.1.0",
"prism-react-renderer": "^2.4.0",
"raw-loader": "^4.0.2",
"react": "^18.3.1",

View File

@@ -1,87 +0,0 @@
module.exports = {
docs: {
Frigate: [
"frigate/index",
"frigate/hardware",
"frigate/installation",
"frigate/camera_setup",
"frigate/video_pipeline",
"frigate/glossary",
],
Guides: [
"guides/getting_started",
"guides/configuring_go2rtc",
"guides/ha_notifications",
"guides/ha_network_storage",
"guides/reverse_proxy",
],
Configuration: {
"Configuration Files": [
"configuration/index",
"configuration/reference",
{
type: "link",
label: "Go2RTC Configuration Reference",
href: "https://github.com/AlexxIT/go2rtc/tree/v1.9.4#configuration",
},
],
Detectors: [
"configuration/object_detectors",
"configuration/audio_detectors",
],
"Semantic Search": [
"configuration/semantic_search",
"configuration/genai",
],
Cameras: [
"configuration/cameras",
"configuration/review",
"configuration/record",
"configuration/snapshots",
"configuration/motion_detection",
"configuration/birdseye",
"configuration/live",
"configuration/restream",
"configuration/autotracking",
"configuration/camera_specific",
],
Objects: [
"configuration/object_filters",
"configuration/masks",
"configuration/zones",
"configuration/objects",
"configuration/stationary_objects",
],
"Extra Configuration": [
"configuration/authentication",
"configuration/notifications",
"configuration/hardware_acceleration",
"configuration/ffmpeg_presets",
"configuration/tls",
"configuration/advanced",
],
},
Integrations: [
"integrations/plus",
"integrations/home-assistant",
"integrations/api",
"integrations/mqtt",
"integrations/third_party_extensions",
],
"Frigate+": [
"plus/index",
"plus/first_model",
"plus/improving_model",
"plus/faq",
],
Troubleshooting: [
"troubleshooting/faqs",
"troubleshooting/recordings",
"troubleshooting/edgetpu",
],
Development: [
"development/contributing",
"development/contributing-boards",
],
},
};

105
docs/sidebars.ts Normal file
View File

@@ -0,0 +1,105 @@
import type { SidebarsConfig, } from '@docusaurus/plugin-content-docs';
import { PropSidebarItemLink } from '@docusaurus/plugin-content-docs';
import frigateHttpApiSidebar from './docs/integrations/api/sidebar';
const sidebars: SidebarsConfig = {
docs: {
Frigate: [
'frigate/index',
'frigate/hardware',
'frigate/installation',
'frigate/camera_setup',
'frigate/video_pipeline',
'frigate/glossary',
],
Guides: [
'guides/getting_started',
'guides/configuring_go2rtc',
'guides/ha_notifications',
'guides/ha_network_storage',
'guides/reverse_proxy',
],
Configuration: {
'Configuration Files': [
'configuration/index',
'configuration/reference',
{
type: 'link',
label: 'Go2RTC Configuration Reference',
href: 'https://github.com/AlexxIT/go2rtc/tree/v1.9.4#configuration',
} as PropSidebarItemLink,
],
Detectors: [
'configuration/object_detectors',
'configuration/audio_detectors',
],
'Semantic Search': [
'configuration/semantic_search',
'configuration/genai',
],
Cameras: [
'configuration/cameras',
'configuration/review',
'configuration/record',
'configuration/snapshots',
'configuration/motion_detection',
'configuration/birdseye',
'configuration/live',
'configuration/restream',
'configuration/autotracking',
'configuration/camera_specific',
],
Objects: [
'configuration/object_filters',
'configuration/masks',
'configuration/zones',
'configuration/objects',
'configuration/stationary_objects',
],
'Extra Configuration': [
'configuration/authentication',
'configuration/notifications',
'configuration/hardware_acceleration',
'configuration/ffmpeg_presets',
"configuration/pwa",
'configuration/tls',
'configuration/advanced',
],
},
Integrations: [
'integrations/plus',
'integrations/home-assistant',
// This is the HTTP API generated by OpenAPI
{
type: 'category',
label: 'HTTP API',
link: {
type: 'generated-index',
title: 'Frigate HTTP API',
description: 'HTTP API',
slug: '/integrations/api/frigate-http-api',
},
items: frigateHttpApiSidebar,
},
'integrations/mqtt',
'integrations/third_party_extensions',
],
'Frigate+': [
'plus/index',
'plus/first_model',
'plus/improving_model',
'plus/faq',
],
Troubleshooting: [
'troubleshooting/faqs',
'troubleshooting/recordings',
'troubleshooting/edgetpu',
],
Development: [
'development/contributing',
'development/contributing-boards',
],
},
};
export default sidebars;

View File

@@ -23,3 +23,214 @@
margin: 0 calc(-1 * var(--ifm-pre-padding));
padding: 0 var(--ifm-pre-padding);
}
/**
Custom CSS for OpenAPI Specification. Based of openapi https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/tree/main/demo
*/
/* Sidebar Method labels */
.api-method > .menu__link,
.schema > .menu__link {
align-items: center;
justify-content: start;
}
.api-method > .menu__link::before,
.schema > .menu__link::before {
width: 55px;
height: 20px;
font-size: 12px;
line-height: 20px;
text-transform: uppercase;
font-weight: 600;
border-radius: 0.25rem;
border: 1px solid;
margin-right: var(--ifm-spacing-horizontal);
text-align: center;
flex-shrink: 0;
border-color: transparent;
color: white;
}
.get > .menu__link::before {
content: "get";
background-color: var(--ifm-color-primary);
}
.post > .menu__link::before {
content: "post";
background-color: var(--ifm-color-success);
}
.delete > .menu__link::before {
content: "del";
background-color: var(--openapi-code-red);
}
.put > .menu__link::before {
content: "put";
background-color: var(--openapi-code-blue);
}
.patch > .menu__link::before {
content: "patch";
background-color: var(--openapi-code-orange);
}
.head > .menu__link::before {
content: "head";
background-color: var(--ifm-color-secondary-darkest);
}
.event > .menu__link::before {
content: "event";
background-color: var(--ifm-color-secondary-darkest);
}
.schema > .menu__link::before {
content: "schema";
background-color: var(--ifm-color-secondary-darkest);
}
.menu__list-item--deprecated > .menu__link,
.menu__list-item--deprecated > .menu__link:hover {
text-decoration: line-through;
}
/* Sidebar Method labels High Contrast */
.api-method-contrast > .menu__link,
.schema-contrast > .menu__link {
align-items: center;
justify-content: start;
}
.api-method-contrast > .menu__link::before,
.schema-contrast > .menu__link::before {
width: 55px;
height: 20px;
font-size: 12px;
line-height: 20px;
text-transform: uppercase;
font-weight: 600;
border-radius: 0.25rem;
border: 1px solid;
border-inline-start-width: 5px;
margin-right: var(--ifm-spacing-horizontal);
text-align: center;
flex-shrink: 0;
}
.get-contrast > .menu__link::before {
content: "get";
background-color: var(--ifm-color-info-contrast-background);
color: var(--ifm-color-info-contrast-foreground);
border-color: var(--ifm-color-info-dark);
}
.post-contrast > .menu__link::before {
content: "post";
background-color: var(--ifm-color-success-contrast-background);
color: var(--ifm-color-success-contrast-foreground);
border-color: var(--ifm-color-success-dark);
}
.delete-contrast > .menu__link::before {
content: "del";
background-color: var(--ifm-color-danger-contrast-background);
color: var(--ifm-color-danger-contrast-foreground);
border-color: var(--ifm-color-danger-dark);
}
.put-contrast > .menu__link::before {
content: "put";
background-color: var(--ifm-color-warning-contrast-background);
color: var(--ifm-color-warning-contrast-foreground);
border-color: var(--ifm-color-warning-dark);
}
.patch-contrast > .menu__link::before {
content: "patch";
background-color: var(--ifm-color-success-contrast-background);
color: var(--ifm-color-success-contrast-foreground);
border-color: var(--ifm-color-success-dark);
}
.head-contrast > .menu__link::before {
content: "head";
background-color: var(--ifm-color-secondary-contrast-background);
color: var(--ifm-color-secondary-contrast-foreground);
border-color: var(--ifm-color-secondary-dark);
}
.event-contrast > .menu__link::before {
content: "event";
background-color: var(--ifm-color-secondary-contrast-background);
color: var(--ifm-color-secondary-contrast-foreground);
border-color: var(--ifm-color-secondary-dark);
}
.schema-contrast > .menu__link::before {
content: "schema";
background-color: var(--ifm-color-secondary-contrast-background);
color: var(--ifm-color-secondary-contrast-foreground);
border-color: var(--ifm-color-secondary-dark);
}
/* Simple */
.api-method-simple > .menu__link {
align-items: center;
justify-content: start;
}
.api-method-simple > .menu__link::before {
width: 55px;
height: 20px;
font-size: 12px;
line-height: 20px;
text-transform: uppercase;
font-weight: 600;
border-radius: 0.25rem;
align-content: start;
margin-right: var(--ifm-spacing-horizontal);
text-align: right;
flex-shrink: 0;
border-color: transparent;
}
.get-simple > .menu__link::before {
content: "get";
color: var(--ifm-color-info);
}
.post-simple > .menu__link::before {
content: "post";
color: var(--ifm-color-success);
}
.delete-simple > .menu__link::before {
content: "del";
color: var(--ifm-color-danger);
}
.put-simple > .menu__link::before {
content: "put";
color: var(--ifm-color-warning);
}
.patch-simple > .menu__link::before {
content: "patch";
color: var(--ifm-color-warning);
}
.head-simple > .menu__link::before {
content: "head";
color: var(--ifm-color-secondary-contrast-foreground);
}
.event-simple > .menu__link::before {
content: "event";
color: var(--ifm-color-secondary-contrast-foreground);
}
.schema-simple > .menu__link::before {
content: "schema";
color: var(--ifm-color-secondary-contrast-foreground);
}

3318
docs/static/frigate-api.yaml vendored Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -1,27 +1,63 @@
import argparse
import faulthandler
import logging
import signal
import sys
import threading
from flask import cli
from pydantic import ValidationError
from frigate.app import FrigateApp
from frigate.config import FrigateConfig
from frigate.log import setup_logging
def main() -> None:
faulthandler.enable()
# Clear all existing handlers.
logging.basicConfig(
level=logging.INFO,
handlers=[],
force=True,
)
# Setup the logging thread
setup_logging()
threading.current_thread().name = "frigate"
cli.show_server_banner = lambda *x: None
# Make sure we exit cleanly on SIGTERM.
signal.signal(signal.SIGTERM, lambda sig, frame: sys.exit())
# Parse the cli arguments.
parser = argparse.ArgumentParser(
prog="Frigate",
description="An NVR with realtime local object detection for IP cameras.",
)
parser.add_argument("--validate-config", action="store_true")
args = parser.parse_args()
# Load the configuration.
try:
config = FrigateConfig.load(install=True)
except ValidationError as e:
print("*************************************************************")
print("*************************************************************")
print("*** Your config file is not valid! ***")
print("*** Please check the docs at ***")
print("*** https://docs.frigate.video/configuration/ ***")
print("*************************************************************")
print("*************************************************************")
print("*** Config Validation Errors ***")
print("*************************************************************")
for error in e.errors():
location = ".".join(str(item) for item in error["loc"])
print(f"{location}: {error['msg']}")
print("*************************************************************")
print("*** End Config Validation Errors ***")
print("*************************************************************")
sys.exit(1)
if args.validate_config:
print("*************************************************************")
print("*** Your config file is valid. ***")
print("*************************************************************")
sys.exit(0)
# Run the main application.
FrigateApp().start()
FrigateApp(config).start()
if __name__ == "__main__":

View File

@@ -7,172 +7,107 @@ import os
import traceback
from datetime import datetime, timedelta
from functools import reduce
from typing import Optional
from typing import Any, Optional
import requests
from flask import Blueprint, Flask, current_app, jsonify, make_response, request
from fastapi import APIRouter, Body, Path, Request, Response
from fastapi.encoders import jsonable_encoder
from fastapi.params import Depends
from fastapi.responses import JSONResponse, PlainTextResponse
from markupsafe import escape
from peewee import operator
from playhouse.sqliteq import SqliteQueueDatabase
from werkzeug.middleware.proxy_fix import ProxyFix
from frigate.api.auth import AuthBp, get_jwt_secret, limiter
from frigate.api.event import EventBp
from frigate.api.export import ExportBp
from frigate.api.media import MediaBp
from frigate.api.notification import NotificationBp
from frigate.api.preview import PreviewBp
from frigate.api.review import ReviewBp
from frigate.api.defs.app_body import AppConfigSetBody
from frigate.api.defs.app_query_parameters import AppTimelineHourlyQueryParameters
from frigate.api.defs.tags import Tags
from frigate.config import FrigateConfig
from frigate.const import CONFIG_DIR
from frigate.embeddings import EmbeddingsContext
from frigate.events.external import ExternalEventProcessor
from frigate.models import Event, Timeline
from frigate.plus import PlusApi
from frigate.ptz.onvif import OnvifController
from frigate.stats.emitter import StatsEmitter
from frigate.storage import StorageMaintainer
from frigate.util.builtin import (
clean_camera_user_pass,
get_tz_modifiers,
update_yaml_from_url,
)
from frigate.util.services import ffprobe_stream, restart_frigate, vainfo_hwaccel
from frigate.util.services import (
ffprobe_stream,
get_nvidia_driver_info,
restart_frigate,
vainfo_hwaccel,
)
from frigate.version import VERSION
logger = logging.getLogger(__name__)
bp = Blueprint("frigate", __name__)
bp.register_blueprint(EventBp)
bp.register_blueprint(ExportBp)
bp.register_blueprint(MediaBp)
bp.register_blueprint(PreviewBp)
bp.register_blueprint(ReviewBp)
bp.register_blueprint(AuthBp)
bp.register_blueprint(NotificationBp)
router = APIRouter(tags=[Tags.app])
def create_app(
frigate_config,
database: SqliteQueueDatabase,
embeddings: Optional[EmbeddingsContext],
detected_frames_processor,
storage_maintainer: StorageMaintainer,
onvif: OnvifController,
external_processor: ExternalEventProcessor,
plus_api: PlusApi,
stats_emitter: StatsEmitter,
):
app = Flask(__name__)
@app.before_request
def check_csrf():
if request.method in ["GET", "HEAD", "OPTIONS", "TRACE"]:
pass
if "origin" in request.headers and "x-csrf-token" not in request.headers:
return jsonify({"success": False, "message": "Missing CSRF header"}), 401
@app.before_request
def _db_connect():
if database.is_closed():
database.connect()
@app.teardown_request
def _db_close(exc):
if not database.is_closed():
database.close()
app.frigate_config = frigate_config
app.embeddings = embeddings
app.detected_frames_processor = detected_frames_processor
app.storage_maintainer = storage_maintainer
app.onvif = onvif
app.external_processor = external_processor
app.plus_api = plus_api
app.camera_error_image = None
app.stats_emitter = stats_emitter
app.jwt_token = get_jwt_secret() if frigate_config.auth.enabled else None
# update the request_address with the x-forwarded-for header from nginx
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1)
# initialize the rate limiter for the login endpoint
limiter.init_app(app)
if frigate_config.auth.failed_login_rate_limit is None:
limiter.enabled = False
app.register_blueprint(bp)
return app
@bp.route("/")
@router.get("/", response_class=PlainTextResponse)
def is_healthy():
return "Frigate is running. Alive and healthy!"
@bp.route("/config/schema.json")
def config_schema():
return current_app.response_class(
current_app.frigate_config.schema_json(), mimetype="application/json"
@router.get("/config/schema.json")
def config_schema(request: Request):
return Response(
content=request.app.frigate_config.schema_json(), media_type="application/json"
)
@bp.route("/go2rtc/streams")
@router.get("/go2rtc/streams")
def go2rtc_streams():
r = requests.get("http://127.0.0.1:1984/api/streams")
if not r.ok:
logger.error("Failed to fetch streams from go2rtc")
return make_response(
jsonify({"success": False, "message": "Error fetching stream data"}),
500,
return JSONResponse(
content=({"success": False, "message": "Error fetching stream data"}),
status_code=500,
)
stream_data = r.json()
for data in stream_data.values():
for producer in data.get("producers", []):
producer["url"] = clean_camera_user_pass(producer.get("url", ""))
return jsonify(stream_data)
return JSONResponse(content=stream_data)
@bp.route("/go2rtc/streams/<camera_name>")
@router.get("/go2rtc/streams/{camera_name}")
def go2rtc_camera_stream(camera_name: str):
r = requests.get(
f"http://127.0.0.1:1984/api/streams?src={camera_name}&video=all&audio=all&microphone"
)
if not r.ok:
logger.error("Failed to fetch streams from go2rtc")
return make_response(
jsonify({"success": False, "message": "Error fetching stream data"}),
500,
return JSONResponse(
content=({"success": False, "message": "Error fetching stream data"}),
status_code=500,
)
stream_data = r.json()
for producer in stream_data.get("producers", []):
producer["url"] = clean_camera_user_pass(producer.get("url", ""))
return jsonify(stream_data)
return JSONResponse(content=stream_data)
@bp.route("/version")
@router.get("/version", response_class=PlainTextResponse)
def version():
return VERSION
@bp.route("/stats")
def stats():
return jsonify(current_app.stats_emitter.get_latest_stats())
@router.get("/stats")
def stats(request: Request):
return JSONResponse(content=request.app.stats_emitter.get_latest_stats())
@bp.route("/stats/history")
def stats_history():
keys = request.args.get("keys", default=None)
@router.get("/stats/history")
def stats_history(request: Request, keys: str = None):
if keys:
keys = keys.split(",")
return jsonify(current_app.stats_emitter.get_stats_history(keys))
return JSONResponse(content=request.app.stats_emitter.get_stats_history(keys))
@bp.route("/config")
def config():
config_obj: FrigateConfig = current_app.frigate_config
@router.get("/config")
def config(request: Request):
config_obj: FrigateConfig = request.app.frigate_config
config: dict[str, dict[str, any]] = config_obj.model_dump(
mode="json", warnings="none", exclude_none=True
)
@@ -183,7 +118,7 @@ def config():
# remove the proxy secret
config["proxy"].pop("auth_secret", None)
for camera_name, camera in current_app.frigate_config.cameras.items():
for camera_name, camera in request.app.frigate_config.cameras.items():
camera_dict = config["cameras"][camera_name]
# clean paths
@@ -199,18 +134,18 @@ def config():
for zone_name, zone in config_obj.cameras[camera_name].zones.items():
camera_dict["zones"][zone_name]["color"] = zone.color
config["plus"] = {"enabled": current_app.plus_api.is_active()}
config["plus"] = {"enabled": request.app.frigate_config.plus_api.is_active()}
config["model"]["colormap"] = config_obj.model.colormap
for detector_config in config["detectors"].values():
detector_config["model"]["labelmap"] = (
current_app.frigate_config.model.merged_labelmap
request.app.frigate_config.model.merged_labelmap
)
return jsonify(config)
return JSONResponse(content=config)
@bp.route("/config/raw")
@router.get("/config/raw")
def config_raw():
config_file = os.environ.get("CONFIG_FILE", "/config/config.yml")
@@ -221,43 +156,44 @@ def config_raw():
config_file = config_file_yaml
if not os.path.isfile(config_file):
return make_response(
jsonify({"success": False, "message": "Could not find file"}), 404
return JSONResponse(
content=({"success": False, "message": "Could not find file"}),
status_code=404,
)
with open(config_file, "r") as f:
raw_config = f.read()
f.close()
return raw_config, 200
return JSONResponse(
content=raw_config, media_type="text/plain", status_code=200
)
@bp.route("/config/save", methods=["POST"])
def config_save():
save_option = request.args.get("save_option")
new_config = request.get_data().decode()
@router.post("/config/save")
def config_save(save_option: str, body: Any = Body(media_type="text/plain")):
new_config = body.decode()
if not new_config:
return make_response(
jsonify(
return JSONResponse(
content=(
{"success": False, "message": "Config with body param is required"}
),
400,
status_code=400,
)
# Validate the config schema
try:
FrigateConfig.parse_raw(new_config)
FrigateConfig.parse_yaml(new_config)
except Exception:
return make_response(
jsonify(
return JSONResponse(
content=(
{
"success": False,
"message": f"\nConfig Error:\n\n{escape(str(traceback.format_exc()))}",
}
),
400,
status_code=400,
)
# Save the config to file
@@ -274,14 +210,14 @@ def config_save():
f.write(new_config)
f.close()
except Exception:
return make_response(
jsonify(
return JSONResponse(
content=(
{
"success": False,
"message": "Could not write config file, be sure that Frigate has write permission on the config file.",
}
),
400,
status_code=400,
)
if save_option == "restart":
@@ -289,34 +225,34 @@ def config_save():
restart_frigate()
except Exception as e:
logging.error(f"Error restarting Frigate: {e}")
return make_response(
jsonify(
return JSONResponse(
content=(
{
"success": True,
"message": "Config successfully saved, unable to restart Frigate",
}
),
200,
status_code=200,
)
return make_response(
jsonify(
return JSONResponse(
content=(
{
"success": True,
"message": "Config successfully saved, restarting (this can take up to one minute)...",
}
),
200,
status_code=200,
)
else:
return make_response(
jsonify({"success": True, "message": "Config successfully saved."}),
200,
return JSONResponse(
content=({"success": True, "message": "Config successfully saved."}),
status_code=200,
)
@bp.route("/config/set", methods=["PUT"])
def config_set():
@router.put("/config/set")
def config_set(request: Request, body: AppConfigSetBody):
config_file = os.environ.get("CONFIG_FILE", f"{CONFIG_DIR}/config.yml")
# Check if we can use .yaml instead of .yml
@@ -330,80 +266,77 @@ def config_set():
f.close()
try:
update_yaml_from_url(config_file, request.url)
update_yaml_from_url(config_file, str(request.url))
with open(config_file, "r") as f:
new_raw_config = f.read()
f.close()
# Validate the config schema
try:
config_obj = FrigateConfig.parse_raw(new_raw_config)
config = FrigateConfig.parse(new_raw_config)
except Exception:
with open(config_file, "w") as f:
f.write(old_raw_config)
f.close()
logger.error(f"\nConfig Error:\n\n{str(traceback.format_exc())}")
return make_response(
jsonify(
return JSONResponse(
content=(
{
"success": False,
"message": "Error parsing config. Check logs for error message.",
}
),
400,
status_code=400,
)
except Exception as e:
logging.error(f"Error updating config: {e}")
return make_response(
jsonify({"success": False, "message": "Error updating config"}),
500,
return JSONResponse(
content=({"success": False, "message": "Error updating config"}),
status_code=500,
)
json = request.get_json(silent=True) or {}
if json.get("requires_restart", 1) == 0:
current_app.frigate_config = FrigateConfig.runtime_config(
config_obj, current_app.plus_api
)
return make_response(
jsonify(
if body.requires_restart == 0:
request.app.frigate_config = config
return JSONResponse(
content=(
{
"success": True,
"message": "Config successfully updated, restart to apply",
}
),
200,
status_code=200,
)
@bp.route("/ffprobe", methods=["GET"])
def ffprobe():
path_param = request.args.get("paths", "")
@router.get("/ffprobe")
def ffprobe(request: Request, paths: str = ""):
path_param = paths
if not path_param:
return make_response(
jsonify({"success": False, "message": "Path needs to be provided."}), 404
return JSONResponse(
content=({"success": False, "message": "Path needs to be provided."}),
status_code=404,
)
if path_param.startswith("camera"):
camera = path_param[7:]
if camera not in current_app.frigate_config.cameras.keys():
return make_response(
jsonify(
if camera not in request.app.frigate_config.cameras.keys():
return JSONResponse(
content=(
{"success": False, "message": f"{camera} is not a valid camera."}
),
404,
status_code=404,
)
if not current_app.frigate_config.cameras[camera].enabled:
return make_response(
jsonify({"success": False, "message": f"{camera} is not enabled."}), 404
if not request.app.frigate_config.cameras[camera].enabled:
return JSONResponse(
content=({"success": False, "message": f"{camera} is not enabled."}),
status_code=404,
)
paths = map(
lambda input: input.path,
current_app.frigate_config.cameras[camera].ffmpeg.inputs,
request.app.frigate_config.cameras[camera].ffmpeg.inputs,
)
elif "," in clean_camera_user_pass(path_param):
paths = path_param.split(",")
@@ -414,7 +347,7 @@ def ffprobe():
output = []
for path in paths:
ffprobe = ffprobe_stream(current_app.frigate_config.ffmpeg, path.strip())
ffprobe = ffprobe_stream(request.app.frigate_config.ffmpeg, path.strip())
output.append(
{
"return_code": ffprobe.returncode,
@@ -431,14 +364,14 @@ def ffprobe():
}
)
return jsonify(output)
return JSONResponse(content=output)
@bp.route("/vainfo", methods=["GET"])
@router.get("/vainfo")
def vainfo():
vainfo = vainfo_hwaccel()
return jsonify(
{
return JSONResponse(
content={
"return_code": vainfo.returncode,
"stderr": (
vainfo.stderr.decode("unicode_escape").strip()
@@ -454,41 +387,49 @@ def vainfo():
)
@bp.route("/logs/<service>", methods=["GET"])
def logs(service: str):
@router.get("/nvinfo")
def nvinfo():
return JSONResponse(content=get_nvidia_driver_info())
@router.get("/logs/{service}", tags=[Tags.logs])
def logs(
service: str = Path(enum=["frigate", "nginx", "go2rtc"]),
download: Optional[str] = None,
start: Optional[int] = 0,
end: Optional[int] = None,
):
"""Get logs for the requested service (frigate/nginx/go2rtc)"""
def download_logs(service_location: str):
try:
file = open(service_location, "r")
contents = file.read()
file.close()
return jsonify(contents)
return JSONResponse(jsonable_encoder(contents))
except FileNotFoundError as e:
logger.error(e)
return make_response(
jsonify({"success": False, "message": "Could not find log file"}),
500,
return JSONResponse(
content={"success": False, "message": "Could not find log file"},
status_code=500,
)
log_locations = {
"frigate": "/dev/shm/logs/frigate/current",
"go2rtc": "/dev/shm/logs/go2rtc/current",
"nginx": "/dev/shm/logs/nginx/current",
"chroma": "/dev/shm/logs/chroma/current",
}
service_location = log_locations.get(service)
if not service_location:
return make_response(
jsonify({"success": False, "message": "Not a valid service"}),
404,
return JSONResponse(
content={"success": False, "message": "Not a valid service"},
status_code=404,
)
if request.args.get("download", type=bool, default=False):
if download:
return download_logs(service_location)
start = request.args.get("start", type=int, default=0)
end = request.args.get("end", type=int)
try:
file = open(service_location, "r")
contents = file.read()
@@ -529,49 +470,47 @@ def logs(service: str):
logLines.append(currentLine)
return make_response(
jsonify({"totalLines": len(logLines), "lines": logLines[start:end]}),
200,
return JSONResponse(
content={"totalLines": len(logLines), "lines": logLines[start:end]},
status_code=200,
)
except FileNotFoundError as e:
logger.error(e)
return make_response(
jsonify({"success": False, "message": "Could not find log file"}),
500,
return JSONResponse(
content={"success": False, "message": "Could not find log file"},
status_code=500,
)
@bp.route("/restart", methods=["POST"])
@router.post("/restart")
def restart():
try:
restart_frigate()
except Exception as e:
logging.error(f"Error restarting Frigate: {e}")
return make_response(
jsonify(
return JSONResponse(
content=(
{
"success": False,
"message": "Unable to restart Frigate.",
}
),
500,
status_code=500,
)
return make_response(
jsonify(
return JSONResponse(
content=(
{
"success": True,
"message": "Restarting (this can take up to one minute)...",
}
),
200,
status_code=200,
)
@bp.route("/labels")
def get_labels():
camera = request.args.get("camera", type=str, default="")
@router.get("/labels")
def get_labels(camera: str = ""):
try:
if camera:
events = Event.select(Event.label).where(Event.camera == camera).distinct()
@@ -579,24 +518,23 @@ def get_labels():
events = Event.select(Event.label).distinct()
except Exception as e:
logger.error(e)
return make_response(
jsonify({"success": False, "message": "Failed to get labels"}), 404
return JSONResponse(
content=({"success": False, "message": "Failed to get labels"}),
status_code=404,
)
labels = sorted([e.label for e in events])
return jsonify(labels)
return JSONResponse(content=labels)
@bp.route("/sub_labels")
def get_sub_labels():
split_joined = request.args.get("split_joined", type=int)
@router.get("/sub_labels")
def get_sub_labels(split_joined: Optional[int] = None):
try:
events = Event.select(Event.sub_label).distinct()
except Exception:
return make_response(
jsonify({"success": False, "message": "Failed to get sub_labels"}),
404,
return JSONResponse(
content=({"success": False, "message": "Failed to get sub_labels"}),
status_code=404,
)
sub_labels = [e.sub_label for e in events]
@@ -617,15 +555,11 @@ def get_sub_labels():
sub_labels.append(part.strip())
sub_labels.sort()
return jsonify(sub_labels)
return JSONResponse(content=sub_labels)
@bp.route("/timeline")
def timeline():
camera = request.args.get("camera", "all")
source_id = request.args.get("source_id", type=str)
limit = request.args.get("limit", 100)
@router.get("/timeline")
def timeline(camera: str = "all", limit: int = 100, source_id: Optional[str] = None):
clauses = []
selected_columns = [
@@ -654,18 +588,18 @@ def timeline():
.dicts()
)
return jsonify([t for t in timeline])
return JSONResponse(content=[t for t in timeline])
@bp.route("/timeline/hourly")
def hourly_timeline():
@router.get("/timeline/hourly")
def hourly_timeline(params: AppTimelineHourlyQueryParameters = Depends()):
"""Get hourly summary for timeline."""
cameras = request.args.get("cameras", "all")
labels = request.args.get("labels", "all")
before = request.args.get("before", type=float)
after = request.args.get("after", type=float)
limit = request.args.get("limit", 200)
tz_name = request.args.get("timezone", default="utc", type=str)
cameras = params.cameras
labels = params.labels
before = params.before
after = params.after
limit = params.limit
tz_name = params.timezone
_, minute_modifier, _ = get_tz_modifiers(tz_name)
minute_offset = int(minute_modifier.split(" ")[0])
@@ -731,8 +665,8 @@ def hourly_timeline():
else:
hours[hour].insert(0, t)
return jsonify(
{
return JSONResponse(
content={
"start": start,
"end": end,
"count": count,

View File

@@ -12,29 +12,49 @@ import time
from datetime import datetime
from pathlib import Path
from flask import Blueprint, current_app, jsonify, make_response, redirect, request
from flask_limiter import Limiter
from fastapi import APIRouter, Request, Response
from fastapi.responses import JSONResponse, RedirectResponse
from joserfc import jwt
from peewee import DoesNotExist
from slowapi import Limiter
from frigate.api.defs.app_body import (
AppPostLoginBody,
AppPostUsersBody,
AppPutPasswordBody,
)
from frigate.api.defs.tags import Tags
from frigate.config import AuthConfig, ProxyConfig
from frigate.const import CONFIG_DIR, JWT_SECRET_ENV_VAR, PASSWORD_HASH_ALGORITHM
from frigate.models import User
logger = logging.getLogger(__name__)
AuthBp = Blueprint("auth", __name__)
router = APIRouter(tags=[Tags.auth])
def get_remote_addr():
class RateLimiter:
_limit = ""
def set_limit(self, limit: str):
self._limit = limit
def get_limit(self) -> str:
return self._limit
rateLimiter = RateLimiter()
def get_remote_addr(request: Request):
route = list(reversed(request.headers.get("x-forwarded-for").split(",")))
logger.debug(f"IP Route: {[r for r in route]}")
trusted_proxies = []
for proxy in current_app.frigate_config.auth.trusted_proxies:
for proxy in request.app.frigate_config.auth.trusted_proxies:
try:
network = ipaddress.ip_network(proxy)
except ValueError:
logger.warn(f"Unable to parse trusted network: {proxy}")
logger.warning(f"Unable to parse trusted network: {proxy}")
trusted_proxies.append(network)
# return the first remote address that is not trusted
@@ -68,16 +88,6 @@ def get_remote_addr():
return request.remote_addr or "127.0.0.1"
limiter = Limiter(
get_remote_addr,
storage_uri="memory://",
)
def get_rate_limit():
return current_app.frigate_config.auth.failed_login_rate_limit
def get_jwt_secret() -> str:
jwt_secret = None
# check env var
@@ -112,7 +122,7 @@ def get_jwt_secret() -> str:
with open(jwt_secret_file, "w") as f:
f.write(str(jwt_secret))
except Exception:
logger.warn(
logger.warning(
"Unable to write jwt token file to config directory. A new jwt token will be created at each startup."
)
else:
@@ -121,18 +131,18 @@ def get_jwt_secret() -> str:
try:
jwt_secret = f.readline()
except Exception:
logger.warn(
logger.warning(
"Unable to read jwt token from .jwt_secret file in config directory. A new jwt token will be created at each startup."
)
jwt_secret = secrets.token_hex(64)
if len(jwt_secret) < 64:
logger.warn("JWT Secret is recommended to be 64 characters or more")
logger.warning("JWT Secret is recommended to be 64 characters or more")
return jwt_secret
def hash_password(password, salt=None, iterations=600000):
def hash_password(password: str, salt=None, iterations=600000):
if salt is None:
salt = secrets.token_hex(16)
assert salt and isinstance(salt, str) and "$" not in salt
@@ -158,33 +168,36 @@ def create_encoded_jwt(user, expiration, secret):
return jwt.encode({"alg": "HS256"}, {"sub": user, "exp": expiration}, secret)
def set_jwt_cookie(response, cookie_name, encoded_jwt, expiration, secure):
def set_jwt_cookie(response: Response, cookie_name, encoded_jwt, expiration, secure):
# TODO: ideally this would set secure as well, but that requires TLS
response.set_cookie(
cookie_name, encoded_jwt, httponly=True, expires=expiration, secure=secure
key=cookie_name,
value=encoded_jwt,
httponly=True,
expires=expiration,
secure=secure,
)
# Endpoint for use with nginx auth_request
@AuthBp.route("/auth")
def auth():
auth_config: AuthConfig = current_app.frigate_config.auth
proxy_config: ProxyConfig = current_app.frigate_config.proxy
@router.get("/auth")
def auth(request: Request):
auth_config: AuthConfig = request.app.frigate_config.auth
proxy_config: ProxyConfig = request.app.frigate_config.proxy
success_response = make_response({}, 202)
success_response = Response("", status_code=202)
# dont require auth if the request is on the internal port
# this header is set by Frigate's nginx proxy, so it cant be spoofed
if request.headers.get("x-server-port", 0, type=int) == 5000:
if int(request.headers.get("x-server-port", default=0)) == 5000:
return success_response
fail_response = make_response({}, 401)
fail_response = Response("", status_code=401)
# ensure the proxy secret matches if configured
if (
proxy_config.auth_secret is not None
and request.headers.get("x-proxy-secret", "", type=str)
!= proxy_config.auth_secret
and request.headers.get("x-proxy-secret", "") != proxy_config.auth_secret
):
logger.debug("X-Proxy-Secret header does not match configured secret value")
return fail_response
@@ -196,7 +209,6 @@ def auth():
if proxy_config.header_map.user is not None:
upstream_user_header_value = request.headers.get(
proxy_config.header_map.user,
type=str,
default="anonymous",
)
success_response.headers["remote-user"] = upstream_user_header_value
@@ -207,10 +219,10 @@ def auth():
# now apply authentication
fail_response.headers["location"] = "/login"
JWT_COOKIE_NAME = current_app.frigate_config.auth.cookie_name
JWT_COOKIE_SECURE = current_app.frigate_config.auth.cookie_secure
JWT_REFRESH = current_app.frigate_config.auth.refresh_time
JWT_SESSION_LENGTH = current_app.frigate_config.auth.session_length
JWT_COOKIE_NAME = request.app.frigate_config.auth.cookie_name
JWT_COOKIE_SECURE = request.app.frigate_config.auth.cookie_secure
JWT_REFRESH = request.app.frigate_config.auth.refresh_time
JWT_SESSION_LENGTH = request.app.frigate_config.auth.session_length
jwt_source = None
encoded_token = None
@@ -230,7 +242,7 @@ def auth():
return fail_response
try:
token = jwt.decode(encoded_token, current_app.jwt_token)
token = jwt.decode(encoded_token, request.app.jwt_token)
if "sub" not in token.claims:
logger.debug("user not set in jwt token")
return fail_response
@@ -266,7 +278,7 @@ def auth():
return fail_response
new_expiration = current_time + JWT_SESSION_LENGTH
new_encoded_jwt = create_encoded_jwt(
user, new_expiration, current_app.jwt_token
user, new_expiration, request.app.jwt_token
)
set_jwt_cookie(
success_response,
@@ -283,86 +295,84 @@ def auth():
return fail_response
@AuthBp.route("/profile")
def profile():
username = request.headers.get("remote-user", type=str)
return jsonify({"username": username})
@router.get("/profile")
def profile(request: Request):
username = request.headers.get("remote-user")
return JSONResponse(content={"username": username})
@AuthBp.route("/logout")
def logout():
auth_config: AuthConfig = current_app.frigate_config.auth
response = make_response(redirect("/login", code=303))
@router.get("/logout")
def logout(request: Request):
auth_config: AuthConfig = request.app.frigate_config.auth
response = RedirectResponse("/login", status_code=303)
response.delete_cookie(auth_config.cookie_name)
return response
@AuthBp.route("/login", methods=["POST"])
@limiter.limit(get_rate_limit, deduct_when=lambda response: response.status_code == 400)
def login():
JWT_COOKIE_NAME = current_app.frigate_config.auth.cookie_name
JWT_COOKIE_SECURE = current_app.frigate_config.auth.cookie_secure
JWT_SESSION_LENGTH = current_app.frigate_config.auth.session_length
content = request.get_json()
user = content["user"]
password = content["password"]
limiter = Limiter(key_func=get_remote_addr)
@router.post("/login")
@limiter.limit(limit_value=rateLimiter.get_limit)
def login(request: Request, body: AppPostLoginBody):
JWT_COOKIE_NAME = request.app.frigate_config.auth.cookie_name
JWT_COOKIE_SECURE = request.app.frigate_config.auth.cookie_secure
JWT_SESSION_LENGTH = request.app.frigate_config.auth.session_length
user = body.user
password = body.password
try:
db_user: User = User.get_by_id(user)
except DoesNotExist:
return make_response({"message": "Login failed"}, 400)
return JSONResponse(content={"message": "Login failed"}, status_code=400)
password_hash = db_user.password_hash
if verify_password(password, password_hash):
expiration = int(time.time()) + JWT_SESSION_LENGTH
encoded_jwt = create_encoded_jwt(user, expiration, current_app.jwt_token)
response = make_response({}, 200)
encoded_jwt = create_encoded_jwt(user, expiration, request.app.jwt_token)
response = Response("", 200)
set_jwt_cookie(
response, JWT_COOKIE_NAME, encoded_jwt, expiration, JWT_COOKIE_SECURE
)
return response
return make_response({"message": "Login failed"}, 400)
return JSONResponse(content={"message": "Login failed"}, status_code=400)
@AuthBp.route("/users")
@router.get("/users")
def get_users():
exports = User.select(User.username).order_by(User.username).dicts().iterator()
return jsonify([e for e in exports])
return JSONResponse([e for e in exports])
@AuthBp.route("/users", methods=["POST"])
def create_user():
HASH_ITERATIONS = current_app.frigate_config.auth.hash_iterations
@router.post("/users")
def create_user(request: Request, body: AppPostUsersBody):
HASH_ITERATIONS = request.app.frigate_config.auth.hash_iterations
request_data = request.get_json()
if not re.match("^[A-Za-z0-9._]+$", body.username):
JSONResponse(content={"message": "Invalid username"}, status_code=400)
if not re.match("^[A-Za-z0-9._]+$", request_data.get("username", "")):
make_response({"message": "Invalid username"}, 400)
password_hash = hash_password(request_data["password"], iterations=HASH_ITERATIONS)
password_hash = hash_password(body.password, iterations=HASH_ITERATIONS)
User.insert(
{
User.username: request_data["username"],
User.username: body.username,
User.password_hash: password_hash,
}
).execute()
return jsonify({"username": request_data["username"]})
return JSONResponse(content={"username": body.username})
@AuthBp.route("/users/<username>", methods=["DELETE"])
@router.delete("/users/{username}")
def delete_user(username: str):
User.delete_by_id(username)
return jsonify({"success": True})
return JSONResponse(content={"success": True})
@AuthBp.route("/users/<username>/password", methods=["PUT"])
def update_password(username: str):
HASH_ITERATIONS = current_app.frigate_config.auth.hash_iterations
@router.put("/users/{username}/password")
def update_password(request: Request, username: str, body: AppPutPasswordBody):
HASH_ITERATIONS = request.app.frigate_config.auth.hash_iterations
request_data = request.get_json()
password_hash = hash_password(request_data["password"], iterations=HASH_ITERATIONS)
password_hash = hash_password(body.password, iterations=HASH_ITERATIONS)
User.set_by_id(
username,
@@ -370,4 +380,4 @@ def update_password(username: str):
User.password_hash: password_hash,
},
)
return jsonify({"success": True})
return JSONResponse(content={"success": True})

View File

@@ -0,0 +1,19 @@
from pydantic import BaseModel
class AppConfigSetBody(BaseModel):
requires_restart: int = 1
class AppPutPasswordBody(BaseModel):
password: str
class AppPostUsersBody(BaseModel):
username: str
password: str
class AppPostLoginBody(BaseModel):
user: str
password: str

View File

@@ -0,0 +1,12 @@
from typing import Optional
from pydantic import BaseModel
class AppTimelineHourlyQueryParameters(BaseModel):
cameras: Optional[str] = "all"
labels: Optional[str] = "all"
after: Optional[float] = None
before: Optional[float] = None
limit: Optional[int] = 200
timezone: Optional[str] = "utc"

View File

@@ -0,0 +1,33 @@
from typing import Optional, Union
from pydantic import BaseModel, Field
class EventsSubLabelBody(BaseModel):
subLabel: str = Field(title="Sub label", max_length=100)
subLabelScore: Optional[float] = Field(
title="Score for sub label", default=None, gt=0.0, le=1.0
)
class EventsDescriptionBody(BaseModel):
description: Union[str, None] = Field(
title="The description of the event", min_length=1
)
class EventsCreateBody(BaseModel):
source_type: Optional[str] = "api"
sub_label: Optional[str] = None
score: Optional[int] = 0
duration: Optional[int] = 30
include_recording: Optional[bool] = True
draw: Optional[dict] = {}
class EventsEndBody(BaseModel):
end_time: Optional[int] = None
class SubmitPlusBody(BaseModel):
include_annotation: int = Field(default=1)

View File

@@ -0,0 +1,53 @@
from typing import Optional
from pydantic import BaseModel
DEFAULT_TIME_RANGE = "00:00,24:00"
class EventsQueryParams(BaseModel):
camera: Optional[str] = "all"
cameras: Optional[str] = "all"
label: Optional[str] = "all"
labels: Optional[str] = "all"
sub_label: Optional[str] = "all"
sub_labels: Optional[str] = "all"
zone: Optional[str] = "all"
zones: Optional[str] = "all"
limit: Optional[int] = 100
after: Optional[float] = None
before: Optional[float] = None
time_range: Optional[str] = DEFAULT_TIME_RANGE
has_clip: Optional[int] = None
has_snapshot: Optional[int] = None
in_progress: Optional[int] = None
include_thumbnails: Optional[int] = 1
favorites: Optional[int] = None
min_score: Optional[float] = None
max_score: Optional[float] = None
is_submitted: Optional[int] = None
min_length: Optional[float] = None
max_length: Optional[float] = None
sort: Optional[str] = None
timezone: Optional[str] = "utc"
class EventsSearchQueryParams(BaseModel):
query: Optional[str] = None
event_id: Optional[str] = None
search_type: Optional[str] = "thumbnail,description"
include_thumbnails: Optional[int] = 1
limit: Optional[int] = 50
cameras: Optional[str] = "all"
labels: Optional[str] = "all"
zones: Optional[str] = "all"
after: Optional[float] = None
before: Optional[float] = None
time_range: Optional[str] = DEFAULT_TIME_RANGE
timezone: Optional[str] = "utc"
class EventsSummaryQueryParams(BaseModel):
timezone: Optional[str] = "utc"
has_clip: Optional[int] = None
has_snapshot: Optional[int] = None

View File

@@ -0,0 +1,42 @@
from enum import Enum
from typing import Optional
from pydantic import BaseModel
class Extension(str, Enum):
webp = "webp"
png = "png"
jpg = "jpg"
jpeg = "jpeg"
class MediaLatestFrameQueryParams(BaseModel):
bbox: Optional[int] = None
timestamp: Optional[int] = None
zones: Optional[int] = None
mask: Optional[int] = None
motion: Optional[int] = None
regions: Optional[int] = None
quality: Optional[int] = 70
height: Optional[int] = None
class MediaEventsSnapshotQueryParams(BaseModel):
download: Optional[bool] = False
timestamp: Optional[int] = None
bbox: Optional[int] = None
crop: Optional[int] = None
height: Optional[int] = None
quality: Optional[int] = 70
class MediaMjpegFeedQueryParams(BaseModel):
fps: int = 3
height: int = 360
bbox: Optional[int] = None
timestamp: Optional[int] = None
zones: Optional[int] = None
mask: Optional[int] = None
motion: Optional[int] = None
regions: Optional[int] = None

View File

@@ -0,0 +1,9 @@
from typing import Optional
from pydantic import BaseModel
from frigate.events.types import RegenerateDescriptionEnum
class RegenerateQueryParameters(BaseModel):
source: Optional[RegenerateDescriptionEnum] = RegenerateDescriptionEnum.thumbnails

View File

@@ -0,0 +1,28 @@
from typing import Optional
from pydantic import BaseModel
class ReviewQueryParams(BaseModel):
cameras: Optional[str] = "all"
labels: Optional[str] = "all"
zones: Optional[str] = "all"
reviewed: Optional[int] = 0
limit: Optional[int] = None
severity: Optional[str] = None
before: Optional[float] = None
after: Optional[float] = None
class ReviewSummaryQueryParams(BaseModel):
cameras: Optional[str] = "all"
labels: Optional[str] = "all"
zones: Optional[str] = "all"
timezone: Optional[str] = "utc"
class ReviewActivityMotionQueryParams(BaseModel):
cameras: Optional[str] = "all"
before: Optional[float] = None
after: Optional[float] = None
scale: Optional[int] = 30

13
frigate/api/defs/tags.py Normal file
View File

@@ -0,0 +1,13 @@
from enum import Enum
class Tags(Enum):
app = "App"
preview = "Preview"
logs = "Logs"
media = "Media"
notifications = "Notifications"
review = "Review"
export = "Export"
events = "Events"
auth = "Auth"

File diff suppressed because it is too large Load Diff

View File

@@ -1,58 +1,56 @@
"""Export apis."""
import logging
import random
import string
from pathlib import Path
from typing import Optional
import psutil
from flask import (
Blueprint,
current_app,
jsonify,
make_response,
request,
)
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse
from peewee import DoesNotExist
from frigate.api.defs.tags import Tags
from frigate.const import EXPORT_DIR
from frigate.models import Export, Recordings
from frigate.record.export import PlaybackFactorEnum, RecordingExporter
logger = logging.getLogger(__name__)
ExportBp = Blueprint("exports", __name__)
router = APIRouter(tags=[Tags.export])
@ExportBp.route("/exports")
@router.get("/exports")
def get_exports():
exports = Export.select().order_by(Export.date.desc()).dicts().iterator()
return jsonify([e for e in exports])
return JSONResponse(content=[e for e in exports])
@ExportBp.route(
"/export/<camera_name>/start/<int:start_time>/end/<int:end_time>", methods=["POST"]
)
@ExportBp.route(
"/export/<camera_name>/start/<float:start_time>/end/<float:end_time>",
methods=["POST"],
)
def export_recording(camera_name: str, start_time, end_time):
if not camera_name or not current_app.frigate_config.cameras.get(camera_name):
return make_response(
jsonify(
@router.post("/export/{camera_name}/start/{start_time}/end/{end_time}")
def export_recording(
request: Request,
camera_name: str,
start_time: float,
end_time: float,
body: dict = None,
):
if not camera_name or not request.app.frigate_config.cameras.get(camera_name):
return JSONResponse(
content=(
{"success": False, "message": f"{camera_name} is not a valid camera."}
),
404,
status_code=404,
)
json: dict[str, any] = request.get_json(silent=True) or {}
json: dict[str, any] = body or {}
playback_factor = json.get("playback", "realtime")
friendly_name: Optional[str] = json.get("name")
if len(friendly_name or "") > 256:
return make_response(
jsonify({"success": False, "message": "File name is too long."}),
401,
return JSONResponse(
content=({"success": False, "message": "File name is too long."}),
status_code=401,
)
existing_image = json.get("image_path")
@@ -69,15 +67,17 @@ def export_recording(camera_name: str, start_time, end_time):
)
if recordings_count <= 0:
return make_response(
jsonify(
return JSONResponse(
content=(
{"success": False, "message": "No recordings found for time range"}
),
400,
status_code=400,
)
export_id = f"{camera_name}_{''.join(random.choices(string.ascii_lowercase + string.digits, k=6))}"
exporter = RecordingExporter(
current_app.frigate_config,
request.app.frigate_config,
export_id,
camera_name,
friendly_name,
existing_image,
@@ -90,58 +90,59 @@ def export_recording(camera_name: str, start_time, end_time):
),
)
exporter.start()
return make_response(
jsonify(
return JSONResponse(
content=(
{
"success": True,
"message": "Starting export of recording.",
"export_id": export_id,
}
),
200,
status_code=200,
)
@ExportBp.route("/export/<id>/<new_name>", methods=["PATCH"])
def export_rename(id, new_name: str):
@router.patch("/export/{event_id}/{new_name}")
def export_rename(event_id: str, new_name: str):
try:
export: Export = Export.get(Export.id == id)
export: Export = Export.get(Export.id == event_id)
except DoesNotExist:
return make_response(
jsonify(
return JSONResponse(
content=(
{
"success": False,
"message": "Export not found.",
}
),
404,
status_code=404,
)
export.name = new_name
export.save()
return make_response(
jsonify(
return JSONResponse(
content=(
{
"success": True,
"message": "Successfully renamed export.",
}
),
200,
status_code=200,
)
@ExportBp.route("/export/<id>", methods=["DELETE"])
def export_delete(id: str):
@router.delete("/export/{event_id}")
def export_delete(event_id: str):
try:
export: Export = Export.get(Export.id == id)
export: Export = Export.get(Export.id == event_id)
except DoesNotExist:
return make_response(
jsonify(
return JSONResponse(
content=(
{
"success": False,
"message": "Export not found.",
}
),
404,
status_code=404,
)
files_in_use = []
@@ -158,11 +159,11 @@ def export_delete(id: str):
continue
if export.video_path.split("/")[-1] in files_in_use:
return make_response(
jsonify(
return JSONResponse(
content=(
{"success": False, "message": "Can not delete in progress export."}
),
400,
status_code=400,
)
Path(export.video_path).unlink(missing_ok=True)
@@ -171,12 +172,12 @@ def export_delete(id: str):
Path(export.thumb_path).unlink(missing_ok=True)
export.delete_instance()
return make_response(
jsonify(
return JSONResponse(
content=(
{
"success": True,
"message": "Successfully deleted export.",
}
),
200,
status_code=200,
)

113
frigate/api/fastapi_app.py Normal file
View File

@@ -0,0 +1,113 @@
import logging
from typing import Optional
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from playhouse.sqliteq import SqliteQueueDatabase
from slowapi import _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from slowapi.middleware import SlowAPIMiddleware
from starlette_context import middleware, plugins
from starlette_context.plugins import Plugin
from frigate.api import app as main_app
from frigate.api import auth, event, export, media, notification, preview, review
from frigate.api.auth import get_jwt_secret, limiter
from frigate.comms.event_metadata_updater import (
EventMetadataPublisher,
)
from frigate.config import FrigateConfig
from frigate.embeddings import EmbeddingsContext
from frigate.events.external import ExternalEventProcessor
from frigate.ptz.onvif import OnvifController
from frigate.stats.emitter import StatsEmitter
from frigate.storage import StorageMaintainer
logger = logging.getLogger(__name__)
def check_csrf(request: Request):
if request.method in ["GET", "HEAD", "OPTIONS", "TRACE"]:
pass
if "origin" in request.headers and "x-csrf-token" not in request.headers:
return JSONResponse(
content={"success": False, "message": "Missing CSRF header"},
status_code=401,
)
# Used to retrieve the remote-user header: https://starlette-context.readthedocs.io/en/latest/plugins.html#easy-mode
class RemoteUserPlugin(Plugin):
key = "Remote-User"
def create_fastapi_app(
frigate_config: FrigateConfig,
database: SqliteQueueDatabase,
embeddings: Optional[EmbeddingsContext],
detected_frames_processor,
storage_maintainer: StorageMaintainer,
onvif: OnvifController,
external_processor: ExternalEventProcessor,
stats_emitter: StatsEmitter,
event_metadata_updater: EventMetadataPublisher,
):
logger.info("Starting FastAPI app")
app = FastAPI(
debug=False,
swagger_ui_parameters={"apisSorter": "alpha", "operationsSorter": "alpha"},
)
# update the request_address with the x-forwarded-for header from nginx
# https://starlette-context.readthedocs.io/en/latest/plugins.html#forwarded-for
app.add_middleware(
middleware.ContextMiddleware,
plugins=(plugins.ForwardedForPlugin(),),
)
# Middleware to connect to DB before and close connection after request
# https://github.com/fastapi/full-stack-fastapi-template/issues/224#issuecomment-737423886
# https://fastapi.tiangolo.com/tutorial/middleware/#before-and-after-the-response
@app.middleware("http")
async def frigate_middleware(request: Request, call_next):
# Before request
check_csrf(request)
if database.is_closed():
database.connect()
response = await call_next(request)
# After request https://stackoverflow.com/a/75487519
if not database.is_closed():
database.close()
return response
# Rate limiter (used for login endpoint)
auth.rateLimiter.set_limit(frigate_config.auth.failed_login_rate_limit or "")
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
app.add_middleware(SlowAPIMiddleware)
# Routes
# Order of include_router matters: https://fastapi.tiangolo.com/tutorial/path-params/#order-matters
app.include_router(auth.router)
app.include_router(review.router)
app.include_router(main_app.router)
app.include_router(preview.router)
app.include_router(notification.router)
app.include_router(export.router)
app.include_router(event.router)
app.include_router(media.router)
# App Properties
app.frigate_config = frigate_config
app.embeddings = embeddings
app.detected_frames_processor = detected_frames_processor
app.storage_maintainer = storage_maintainer
app.camera_error_image = None
app.onvif = onvif
app.stats_emitter = stats_emitter
app.event_metadata_updater = event_metadata_updater
app.external_processor = external_processor
app.jwt_token = get_jwt_secret() if frigate_config.auth.enabled else None
return app

File diff suppressed because it is too large Load Diff

View File

@@ -4,62 +4,62 @@ import logging
import os
from cryptography.hazmat.primitives import serialization
from flask import (
Blueprint,
current_app,
jsonify,
make_response,
request,
)
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse
from peewee import DoesNotExist
from py_vapid import Vapid01, utils
from frigate.api.defs.tags import Tags
from frigate.const import CONFIG_DIR
from frigate.models import User
logger = logging.getLogger(__name__)
NotificationBp = Blueprint("notifications", __name__)
router = APIRouter(tags=[Tags.notifications])
@NotificationBp.route("/notifications/pubkey", methods=["GET"])
def get_vapid_pub_key():
if not current_app.frigate_config.notifications.enabled:
return make_response(
jsonify({"success": False, "message": "Notifications are not enabled."}),
400,
@router.get("/notifications/pubkey")
def get_vapid_pub_key(request: Request):
if not request.app.frigate_config.notifications.enabled:
return JSONResponse(
content=({"success": False, "message": "Notifications are not enabled."}),
status_code=400,
)
key = Vapid01.from_file(os.path.join(CONFIG_DIR, "notifications.pem"))
raw_pub = key.public_key.public_bytes(
serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint
)
return jsonify(utils.b64urlencode(raw_pub)), 200
return JSONResponse(content=utils.b64urlencode(raw_pub), status_code=200)
@NotificationBp.route("/notifications/register", methods=["POST"])
def register_notifications():
if current_app.frigate_config.auth.enabled:
username = request.headers.get("remote-user", type=str) or "admin"
@router.post("/notifications/register")
def register_notifications(request: Request, body: dict = None):
if request.app.frigate_config.auth.enabled:
# FIXME: For FastAPI the remote-user is not being populated
username = request.headers.get("remote-user") or "admin"
else:
username = "admin"
json: dict[str, any] = request.get_json(silent=True) or {}
json: dict[str, any] = body or {}
sub = json.get("sub")
if not sub:
return jsonify(
{"success": False, "message": "Subscription must be provided."}
), 400
return JSONResponse(
content={"success": False, "message": "Subscription must be provided."},
status_code=400,
)
try:
User.update(notification_tokens=User.notification_tokens.append(sub)).where(
User.username == username
).execute()
return make_response(
jsonify({"success": True, "message": "Successfully saved token."}), 200
return JSONResponse(
content=({"success": True, "message": "Successfully saved token."}),
status_code=200,
)
except DoesNotExist:
return make_response(
jsonify({"success": False, "message": "Could not find user."}), 404
return JSONResponse(
content=({"success": False, "message": "Could not find user."}),
status_code=404,
)

View File

@@ -5,23 +5,21 @@ import os
from datetime import datetime, timedelta, timezone
import pytz
from flask import (
Blueprint,
jsonify,
make_response,
)
from fastapi import APIRouter
from fastapi.responses import JSONResponse
from frigate.api.defs.tags import Tags
from frigate.const import CACHE_DIR, PREVIEW_FRAME_TYPE
from frigate.models import Previews
logger = logging.getLogger(__name__)
PreviewBp = Blueprint("previews", __name__)
router = APIRouter(tags=[Tags.preview])
@PreviewBp.route("/preview/<camera_name>/start/<int:start_ts>/end/<int:end_ts>")
@PreviewBp.route("/preview/<camera_name>/start/<float:start_ts>/end/<float:end_ts>")
def preview_ts(camera_name, start_ts, end_ts):
@router.get("/preview/{camera_name}/start/{start_ts}/end/{end_ts}")
def preview_ts(camera_name: str, start_ts: float, end_ts: float):
"""Get all mp4 previews relevant for time period."""
if camera_name != "all":
camera_clause = Previews.camera == camera_name
@@ -62,24 +60,20 @@ def preview_ts(camera_name, start_ts, end_ts):
)
if not clips:
return make_response(
jsonify(
{
"success": False,
"message": "No previews found.",
}
),
404,
return JSONResponse(
content={
"success": False,
"message": "No previews found.",
},
status_code=404,
)
return make_response(jsonify(clips), 200)
return JSONResponse(content=clips, status_code=200)
@PreviewBp.route("/preview/<year_month>/<int:day>/<int:hour>/<camera_name>/<tz_name>")
@PreviewBp.route(
"/preview/<year_month>/<float:day>/<float:hour>/<camera_name>/<tz_name>"
)
def preview_hour(year_month, day, hour, camera_name, tz_name):
@router.get("/preview/{year_month}/{day}/{hour}/{camera_name}/{tz_name}")
def preview_hour(year_month: str, day: int, hour: int, camera_name: str, tz_name: str):
"""Get all mp4 previews relevant for time period given the timezone"""
parts = year_month.split("-")
start_date = (
datetime(int(parts[0]), int(parts[1]), int(day), int(hour), tzinfo=timezone.utc)
@@ -92,11 +86,8 @@ def preview_hour(year_month, day, hour, camera_name, tz_name):
return preview_ts(camera_name, start_ts, end_ts)
@PreviewBp.route("/preview/<camera_name>/start/<int:start_ts>/end/<int:end_ts>/frames")
@PreviewBp.route(
"/preview/<camera_name>/start/<float:start_ts>/end/<float:end_ts>/frames"
)
def get_preview_frames_from_cache(camera_name: str, start_ts, end_ts):
@router.get("/preview/{camera_name}/start/{start_ts}/end/{end_ts}/frames")
def get_preview_frames_from_cache(camera_name: str, start_ts: float, end_ts: float):
"""Get list of cached preview frames"""
preview_dir = os.path.join(CACHE_DIR, "preview_frames")
file_start = f"preview_{camera_name}"
@@ -116,4 +107,7 @@ def get_preview_frames_from_cache(camera_name: str, start_ts, end_ts):
selected_previews.append(file)
return jsonify(selected_previews)
return JSONResponse(
content=selected_previews,
status_code=200,
)

View File

@@ -1,35 +1,43 @@
"""Review apis."""
import datetime
import logging
from datetime import datetime, timedelta
from functools import reduce
from pathlib import Path
import pandas as pd
from flask import Blueprint, jsonify, make_response, request
from fastapi import APIRouter
from fastapi.params import Depends
from fastapi.responses import JSONResponse
from peewee import Case, DoesNotExist, fn, operator
from playhouse.shortcuts import model_to_dict
from frigate.api.defs.review_query_parameters import (
ReviewActivityMotionQueryParams,
ReviewQueryParams,
ReviewSummaryQueryParams,
)
from frigate.api.defs.tags import Tags
from frigate.models import Recordings, ReviewSegment
from frigate.util.builtin import get_tz_modifiers
logger = logging.getLogger(__name__)
ReviewBp = Blueprint("reviews", __name__)
router = APIRouter(tags=[Tags.review])
@ReviewBp.route("/review")
def review():
cameras = request.args.get("cameras", "all")
labels = request.args.get("labels", "all")
zones = request.args.get("zones", "all")
reviewed = request.args.get("reviewed", type=int, default=0)
limit = request.args.get("limit", type=int, default=None)
severity = request.args.get("severity", None)
before = request.args.get("before", type=float, default=datetime.now().timestamp())
after = request.args.get(
"after", type=float, default=(datetime.now() - timedelta(hours=24)).timestamp()
@router.get("/review")
def review(params: ReviewQueryParams = Depends()):
cameras = params.cameras
labels = params.labels
zones = params.zones
reviewed = params.reviewed
limit = params.limit
severity = params.severity
before = params.before or datetime.datetime.now().timestamp()
after = (
params.after
or (datetime.datetime.now() - datetime.timedelta(hours=24)).timestamp()
)
clauses = [
@@ -91,39 +99,18 @@ def review():
.iterator()
)
return jsonify([r for r in review])
return JSONResponse(content=[r for r in review])
@ReviewBp.route("/review/event/<id>")
def get_review_from_event(id: str):
try:
return model_to_dict(
ReviewSegment.get(
ReviewSegment.data["detections"].cast("text") % f'*"{id}"*'
)
)
except DoesNotExist:
return "Review item not found", 404
@router.get("/review/summary")
def review_summary(params: ReviewSummaryQueryParams = Depends()):
hour_modifier, minute_modifier, seconds_offset = get_tz_modifiers(params.timezone)
day_ago = (datetime.datetime.now() - datetime.timedelta(hours=24)).timestamp()
month_ago = (datetime.datetime.now() - datetime.timedelta(days=30)).timestamp()
@ReviewBp.route("/review/<id>")
def get_review(id: str):
try:
return model_to_dict(ReviewSegment.get(ReviewSegment.id == id))
except DoesNotExist:
return "Review item not found", 404
@ReviewBp.route("/review/summary")
def review_summary():
tz_name = request.args.get("timezone", default="utc", type=str)
hour_modifier, minute_modifier, seconds_offset = get_tz_modifiers(tz_name)
day_ago = (datetime.now() - timedelta(hours=24)).timestamp()
month_ago = (datetime.now() - timedelta(days=30)).timestamp()
cameras = request.args.get("cameras", "all")
labels = request.args.get("labels", "all")
zones = request.args.get("zones", "all")
cameras = params.cameras
labels = params.labels
zones = params.zones
clauses = [(ReviewSegment.start_time > day_ago)]
@@ -358,53 +345,39 @@ def review_summary():
for e in last_month.dicts().iterator():
data[e["day"]] = e
return jsonify(data)
return JSONResponse(content=data)
@ReviewBp.route("/reviews/viewed", methods=("POST",))
def set_multiple_reviewed():
json: dict[str, any] = request.get_json(silent=True) or {}
@router.post("/reviews/viewed")
def set_multiple_reviewed(body: dict = None):
json: dict[str, any] = body or {}
list_of_ids = json.get("ids", "")
if not list_of_ids or len(list_of_ids) == 0:
return make_response(
jsonify({"success": False, "message": "Not a valid list of ids"}), 404
return JSONResponse(
context=({"success": False, "message": "Not a valid list of ids"}),
status_code=404,
)
ReviewSegment.update(has_been_reviewed=True).where(
ReviewSegment.id << list_of_ids
).execute()
return make_response(
jsonify({"success": True, "message": "Reviewed multiple items"}), 200
return JSONResponse(
content=({"success": True, "message": "Reviewed multiple items"}),
status_code=200,
)
@ReviewBp.route("/review/<id>/viewed", methods=("DELETE",))
def set_not_reviewed(id):
try:
review: ReviewSegment = ReviewSegment.get(ReviewSegment.id == id)
except DoesNotExist:
return make_response(
jsonify({"success": False, "message": "Review " + id + " not found"}), 404
)
review.has_been_reviewed = False
review.save()
return make_response(
jsonify({"success": True, "message": "Reviewed " + id + " not viewed"}), 200
)
@ReviewBp.route("/reviews/delete", methods=("POST",))
def delete_reviews():
json: dict[str, any] = request.get_json(silent=True) or {}
@router.post("/reviews/delete")
def delete_reviews(body: dict = None):
json: dict[str, any] = body or {}
list_of_ids = json.get("ids", "")
if not list_of_ids or len(list_of_ids) == 0:
return make_response(
jsonify({"success": False, "message": "Not a valid list of ids"}), 404
return JSONResponse(
content=({"success": False, "message": "Not a valid list of ids"}),
status_code=404,
)
reviews = (
@@ -446,18 +419,23 @@ def delete_reviews():
Recordings.delete().where(Recordings.id << recording_ids).execute()
ReviewSegment.delete().where(ReviewSegment.id << list_of_ids).execute()
return make_response(jsonify({"success": True, "message": "Delete reviews"}), 200)
@ReviewBp.route("/review/activity/motion")
def motion_activity():
"""Get motion and audio activity."""
cameras = request.args.get("cameras", "all")
before = request.args.get("before", type=float, default=datetime.now().timestamp())
after = request.args.get(
"after", type=float, default=(datetime.now() - timedelta(hours=1)).timestamp()
return JSONResponse(
content=({"success": True, "message": "Delete reviews"}), status_code=200
)
@router.get("/review/activity/motion")
def motion_activity(params: ReviewActivityMotionQueryParams = Depends()):
"""Get motion and audio activity."""
cameras = params.cameras
before = params.before or datetime.datetime.now().timestamp()
after = (
params.after
or (datetime.datetime.now() - datetime.timedelta(hours=1)).timestamp()
)
# get scale in seconds
scale = params.scale
clauses = [(Recordings.start_time > after) & (Recordings.end_time < before)]
clauses.append((Recordings.motion > 0))
@@ -477,15 +455,12 @@ def motion_activity():
.iterator()
)
# get scale in seconds
scale = request.args.get("scale", type=int, default=30)
# resample data using pandas to get activity on scaled basis
df = pd.DataFrame(data, columns=["start_time", "motion", "camera"])
if df.empty:
logger.warning("No motion data found for the requested time range")
return jsonify([])
return JSONResponse(content=[])
df = df.astype(dtype={"motion": "float32"})
@@ -520,17 +495,20 @@ def motion_activity():
# change types for output
df.index = df.index.astype(int) // (10**9)
normalized = df.reset_index().to_dict("records")
return jsonify(normalized)
return JSONResponse(content=normalized)
@ReviewBp.route("/review/activity/audio")
def audio_activity():
@router.get("/review/activity/audio")
def audio_activity(params: ReviewActivityMotionQueryParams = Depends()):
"""Get motion and audio activity."""
cameras = request.args.get("cameras", "all")
before = request.args.get("before", type=float, default=datetime.now().timestamp())
after = request.args.get(
"after", type=float, default=(datetime.now() - timedelta(hours=1)).timestamp()
cameras = params.cameras
before = params.before or datetime.datetime.now().timestamp()
after = (
params.after
or (datetime.datetime.now() - datetime.timedelta(hours=1)).timestamp()
)
# get scale in seconds
scale = params.scale
clauses = [(Recordings.start_time > after) & (Recordings.end_time < before)]
@@ -562,9 +540,6 @@ def audio_activity():
}
)
# get scale in seconds
scale = request.args.get("scale", type=int, default=30)
# resample data using pandas to get activity on scaled basis
df = pd.DataFrame(data, columns=["start_time", "audio"])
df = df.astype(dtype={"audio": "float16"})
@@ -584,4 +559,45 @@ def audio_activity():
# change types for output
df.index = df.index.astype(int) // (10**9)
normalized = df.reset_index().to_dict("records")
return jsonify(normalized)
return JSONResponse(content=normalized)
@router.get("/review/event/{event_id}")
def get_review_from_event(event_id: str):
try:
return model_to_dict(
ReviewSegment.get(
ReviewSegment.data["detections"].cast("text") % f'*"{event_id}"*'
)
)
except DoesNotExist:
return "Review item not found", 404
@router.get("/review/{event_id}")
def get_review(event_id: str):
try:
return model_to_dict(ReviewSegment.get(ReviewSegment.id == event_id))
except DoesNotExist:
return "Review item not found", 404
@router.delete("/review/{event_id}/viewed")
def set_not_reviewed(event_id: str):
try:
review: ReviewSegment = ReviewSegment.get(ReviewSegment.id == event_id)
except DoesNotExist:
return JSONResponse(
content=(
{"success": False, "message": "Review " + event_id + " not found"}
),
status_code=404,
)
review.has_been_reviewed = False
review.save()
return JSONResponse(
content=({"success": True, "message": "Reviewed " + event_id + " not viewed"}),
status_code=200,
)

View File

@@ -1,49 +1,48 @@
import argparse
import datetime
import logging
import multiprocessing as mp
import os
import secrets
import shutil
import signal
import sys
import traceback
from multiprocessing import Queue
from multiprocessing.synchronize import Event as MpEvent
from types import FrameType
from typing import Optional
import psutil
import uvicorn
from peewee_migrate import Router
from playhouse.sqlite_ext import SqliteExtDatabase
from playhouse.sqliteq import SqliteQueueDatabase
from pydantic import ValidationError
from frigate.api.app import create_app
import frigate.util as util
from frigate.api.auth import hash_password
from frigate.api.fastapi_app import create_fastapi_app
from frigate.camera import CameraMetrics, PTZMetrics
from frigate.comms.config_updater import ConfigPublisher
from frigate.comms.dispatcher import Communicator, Dispatcher
from frigate.comms.event_metadata_updater import (
EventMetadataPublisher,
EventMetadataTypeEnum,
)
from frigate.comms.inter_process import InterProcessCommunicator
from frigate.comms.mqtt import MqttClient
from frigate.comms.webpush import WebPushClient
from frigate.comms.ws import WebSocketClient
from frigate.comms.zmq_proxy import ZmqProxy
from frigate.config import FrigateConfig
from frigate.config.config import FrigateConfig
from frigate.const import (
CACHE_DIR,
CLIPS_DIR,
CONFIG_DIR,
DEFAULT_DB_PATH,
EXPORT_DIR,
MODEL_CACHE_DIR,
RECORD_DIR,
)
from frigate.db.sqlitevecq import SqliteVecQueueDatabase
from frigate.embeddings import EmbeddingsContext, manage_embeddings
from frigate.events.audio import listen_to_audio
from frigate.events.audio import AudioProcessor
from frigate.events.cleanup import EventCleanup
from frigate.events.external import ExternalEventProcessor
from frigate.events.maintainer import EventProcessor
from frigate.log import log_thread
from frigate.models import (
Event,
Export,
@@ -58,7 +57,6 @@ from frigate.models import (
from frigate.object_detection import ObjectDetectProcess
from frigate.object_processing import TrackedObjectProcessor
from frigate.output.output import output_frames
from frigate.plus import PlusApi
from frigate.ptz.autotrack import PtzAutoTrackerThread
from frigate.ptz.onvif import OnvifController
from frigate.record.cleanup import RecordingCleanup
@@ -69,9 +67,7 @@ from frigate.stats.emitter import StatsEmitter
from frigate.stats.util import stats_init
from frigate.storage import StorageMaintainer
from frigate.timeline import TimelineProcessor
from frigate.types import CameraMetricsTypes, PTZMetricsTypes
from frigate.util.builtin import empty_and_close_queue, save_default_config
from frigate.util.config import migrate_frigate_config
from frigate.util.builtin import empty_and_close_queue
from frigate.util.object import get_camera_regions_grid
from frigate.version import VERSION
from frigate.video import capture_camera, track_camera
@@ -81,22 +77,20 @@ logger = logging.getLogger(__name__)
class FrigateApp:
def __init__(self) -> None:
def __init__(self, config: FrigateConfig) -> None:
self.audio_process: Optional[mp.Process] = None
self.stop_event: MpEvent = mp.Event()
self.detection_queue: Queue = mp.Queue()
self.detectors: dict[str, ObjectDetectProcess] = {}
self.detection_out_events: dict[str, MpEvent] = {}
self.detection_shms: list[mp.shared_memory.SharedMemory] = []
self.log_queue: Queue = mp.Queue()
self.plus_api = PlusApi()
self.camera_metrics: dict[str, CameraMetricsTypes] = {}
self.ptz_metrics: dict[str, PTZMetricsTypes] = {}
self.camera_metrics: dict[str, CameraMetrics] = {}
self.ptz_metrics: dict[str, PTZMetrics] = {}
self.processes: dict[str, int] = {}
self.embeddings: Optional[EmbeddingsContext] = None
self.region_grids: dict[str, list[list[dict[str, int]]]] = {}
def set_environment_vars(self) -> None:
for key, value in self.config.environment_vars.items():
os.environ[key] = value
self.config = config
def ensure_dirs(self) -> None:
for d in [
@@ -113,94 +107,15 @@ class FrigateApp:
else:
logger.debug(f"Skipping directory: {d}")
def init_config(self) -> None:
config_file = os.environ.get("CONFIG_FILE", "/config/config.yml")
# Check if we can use .yaml instead of .yml
config_file_yaml = config_file.replace(".yml", ".yaml")
if os.path.isfile(config_file_yaml):
config_file = config_file_yaml
if not os.path.isfile(config_file):
print("No config file found, saving default config")
config_file = config_file_yaml
save_default_config(config_file)
# check if the config file needs to be migrated
migrate_frigate_config(config_file)
user_config = FrigateConfig.parse_file(config_file)
self.config = user_config.runtime_config(self.plus_api)
def init_camera_metrics(self) -> None:
# create camera_metrics
for camera_name in self.config.cameras.keys():
# create camera_metrics
self.camera_metrics[camera_name] = {
"camera_fps": mp.Value("d", 0.0), # type: ignore[typeddict-item]
# issue https://github.com/python/typeshed/issues/8799
# from mypy 0.981 onwards
"skipped_fps": mp.Value("d", 0.0), # type: ignore[typeddict-item]
# issue https://github.com/python/typeshed/issues/8799
# from mypy 0.981 onwards
"process_fps": mp.Value("d", 0.0), # type: ignore[typeddict-item]
"detection_fps": mp.Value("d", 0.0), # type: ignore[typeddict-item]
# issue https://github.com/python/typeshed/issues/8799
# from mypy 0.981 onwards
"detection_frame": mp.Value("d", 0.0), # type: ignore[typeddict-item]
# issue https://github.com/python/typeshed/issues/8799
# from mypy 0.981 onwards
"read_start": mp.Value("d", 0.0), # type: ignore[typeddict-item]
# issue https://github.com/python/typeshed/issues/8799
# from mypy 0.981 onwards
"ffmpeg_pid": mp.Value("i", 0), # type: ignore[typeddict-item]
# issue https://github.com/python/typeshed/issues/8799
# from mypy 0.981 onwards
"frame_queue": mp.Queue(maxsize=2),
"capture_process": None,
"process": None,
"audio_rms": mp.Value("d", 0.0), # type: ignore[typeddict-item]
"audio_dBFS": mp.Value("d", 0.0), # type: ignore[typeddict-item]
}
self.ptz_metrics[camera_name] = {
"ptz_autotracker_enabled": mp.Value( # type: ignore[typeddict-item]
# issue https://github.com/python/typeshed/issues/8799
# from mypy 0.981 onwards
"i",
self.config.cameras[camera_name].onvif.autotracking.enabled,
),
"ptz_tracking_active": mp.Event(),
"ptz_motor_stopped": mp.Event(),
"ptz_reset": mp.Event(),
"ptz_start_time": mp.Value("d", 0.0), # type: ignore[typeddict-item]
# issue https://github.com/python/typeshed/issues/8799
# from mypy 0.981 onwards
"ptz_stop_time": mp.Value("d", 0.0), # type: ignore[typeddict-item]
# issue https://github.com/python/typeshed/issues/8799
# from mypy 0.981 onwards
"ptz_frame_time": mp.Value("d", 0.0), # type: ignore[typeddict-item]
# issue https://github.com/python/typeshed/issues/8799
# from mypy 0.981 onwards
"ptz_zoom_level": mp.Value("d", 0.0), # type: ignore[typeddict-item]
# issue https://github.com/python/typeshed/issues/8799
# from mypy 0.981 onwards
"ptz_max_zoom": mp.Value("d", 0.0), # type: ignore[typeddict-item]
# issue https://github.com/python/typeshed/issues/8799
# from mypy 0.981 onwards
"ptz_min_zoom": mp.Value("d", 0.0), # type: ignore[typeddict-item]
# issue https://github.com/python/typeshed/issues/8799
# from mypy 0.981 onwards
}
self.ptz_metrics[camera_name]["ptz_motor_stopped"].set()
def set_log_levels(self) -> None:
logging.getLogger().setLevel(self.config.logger.default.value.upper())
for log, level in self.config.logger.logs.items():
logging.getLogger(log).setLevel(level.value.upper())
if "werkzeug" not in self.config.logger.logs:
logging.getLogger("werkzeug").setLevel("ERROR")
if "ws4py" not in self.config.logger.logs:
logging.getLogger("ws4py").setLevel("ERROR")
self.camera_metrics[camera_name] = CameraMetrics()
self.ptz_metrics[camera_name] = PTZMetrics(
autotracker_enabled=self.config.cameras[
camera_name
].onvif.autotracking.enabled
)
def init_queues(self) -> None:
# Queue for cameras to push tracked objects to
@@ -233,13 +148,6 @@ class FrigateApp:
except PermissionError:
logger.error("Unable to write to /config to save DB state")
# Migrate DB location
old_db_path = DEFAULT_DB_PATH
if not os.path.isfile(self.config.database.path) and os.path.isfile(
old_db_path
):
os.rename(old_db_path, self.config.database.path)
# Migrate DB schema
migrate_db = SqliteExtDatabase(self.config.database.path)
@@ -288,7 +196,7 @@ class FrigateApp:
self.processes["go2rtc"] = proc.info["pid"]
def init_recording_manager(self) -> None:
recording_process = mp.Process(
recording_process = util.Process(
target=manage_recordings,
name="recording_manager",
args=(self.config,),
@@ -300,7 +208,7 @@ class FrigateApp:
logger.info(f"Recording process started: {recording_process.pid}")
def init_review_segment_manager(self) -> None:
review_segment_process = mp.Process(
review_segment_process = util.Process(
target=manage_review_segments,
name="review_segment_manager",
args=(self.config,),
@@ -313,12 +221,9 @@ class FrigateApp:
def init_embeddings_manager(self) -> None:
if not self.config.semantic_search.enabled:
self.embeddings = None
return
# Create a client for other processes to use
self.embeddings = EmbeddingsContext()
embedding_process = mp.Process(
embedding_process = util.Process(
target=manage_embeddings,
name="embeddings_manager",
args=(self.config,),
@@ -332,7 +237,7 @@ class FrigateApp:
def bind_database(self) -> None:
"""Bind db to the main process."""
# NOTE: all db accessing processes need to be created before the db can be bound to the main process
self.db = SqliteQueueDatabase(
self.db = SqliteVecQueueDatabase(
self.config.database.path,
pragmas={
"auto_vacuum": "FULL", # Does not defragment database
@@ -342,6 +247,7 @@ class FrigateApp:
timeout=max(
60, 10 * len([c for c in self.config.cameras.values() if c.enabled])
),
load_vec_extension=self.config.semantic_search.enabled,
)
models = [
Event,
@@ -365,7 +271,12 @@ class FrigateApp:
except PermissionError:
logger.error("Unable to write to /config to save export state")
migrate_exports(self.config.ffmpeg, self.config.cameras.keys())
migrate_exports(self.config.ffmpeg, list(self.config.cameras.keys()))
def init_embeddings_client(self) -> None:
if self.config.semantic_search.enabled:
# Create a client for other processes to use
self.embeddings = EmbeddingsContext(self.db)
def init_external_event_processor(self) -> None:
self.external_event_processor = ExternalEventProcessor(self.config)
@@ -373,20 +284,10 @@ class FrigateApp:
def init_inter_process_communicator(self) -> None:
self.inter_process_communicator = InterProcessCommunicator()
self.inter_config_updater = ConfigPublisher()
self.inter_zmq_proxy = ZmqProxy()
def init_web_server(self) -> None:
self.flask_app = create_app(
self.config,
self.db,
self.embeddings,
self.detected_frames_processor,
self.storage_maintainer,
self.onvif_controller,
self.external_event_processor,
self.plus_api,
self.stats_emitter,
self.event_metadata_updater = EventMetadataPublisher(
EventMetadataTypeEnum.regenerate_description
)
self.inter_zmq_proxy = ZmqProxy()
def init_onvif(self) -> None:
self.onvif_controller = OnvifController(self.config, self.ptz_metrics)
@@ -419,7 +320,9 @@ class FrigateApp:
largest_frame = max(
[
det.model.height * det.model.width * 3
for (name, det) in self.config.detectors.items()
if det.model is not None
else 320
for det in self.config.detectors.values()
]
)
shm_in = mp.shared_memory.SharedMemory(
@@ -469,7 +372,7 @@ class FrigateApp:
self.detected_frames_processor.start()
def start_video_output_processor(self) -> None:
output_processor = mp.Process(
output_processor = util.Process(
target=output_frames,
name="output_processor",
args=(self.config,),
@@ -486,6 +389,7 @@ class FrigateApp:
# create or update region grids for each camera
for camera in self.config.cameras.values():
assert camera.name is not None
self.region_grids[camera.name] = get_camera_regions_grid(
camera.name,
camera.detect,
@@ -498,7 +402,7 @@ class FrigateApp:
logger.info(f"Camera processor not started for disabled camera {name}")
continue
camera_process = mp.Process(
camera_process = util.Process(
target=track_camera,
name=f"camera_processor:{name}",
args=(
@@ -513,43 +417,41 @@ class FrigateApp:
self.ptz_metrics[name],
self.region_grids[name],
),
daemon=True,
)
camera_process.daemon = True
self.camera_metrics[name]["process"] = camera_process
self.camera_metrics[name].process = camera_process
camera_process.start()
logger.info(f"Camera processor started for {name}: {camera_process.pid}")
def start_camera_capture_processes(self) -> None:
shm_frame_count = self.shm_frame_count()
for name, config in self.config.cameras.items():
if not self.config.cameras[name].enabled:
logger.info(f"Capture process not started for disabled camera {name}")
continue
capture_process = mp.Process(
capture_process = util.Process(
target=capture_camera,
name=f"camera_capture:{name}",
args=(name, config, self.shm_frame_count, self.camera_metrics[name]),
args=(name, config, shm_frame_count, self.camera_metrics[name]),
)
capture_process.daemon = True
self.camera_metrics[name]["capture_process"] = capture_process
self.camera_metrics[name].capture_process = capture_process
capture_process.start()
logger.info(f"Capture process started for {name}: {capture_process.pid}")
def start_audio_processors(self) -> None:
self.audio_process = None
if len([c for c in self.config.cameras.values() if c.audio.enabled]) > 0:
self.audio_process = mp.Process(
target=listen_to_audio,
name="audio_capture",
args=(
self.config,
self.camera_metrics,
),
)
self.audio_process.daemon = True
def start_audio_processor(self) -> None:
audio_cameras = [
c
for c in self.config.cameras.values()
if c.enabled and c.audio.enabled_in_config
]
if audio_cameras:
self.audio_process = AudioProcessor(audio_cameras, self.camera_metrics)
self.audio_process.start()
self.processes["audio_detector"] = self.audio_process.pid or 0
logger.info(f"Audio process started: {self.audio_process.pid}")
def start_timeline_processor(self) -> None:
self.timeline_processor = TimelineProcessor(
@@ -566,7 +468,7 @@ class FrigateApp:
self.event_processor.start()
def start_event_cleanup(self) -> None:
self.event_cleanup = EventCleanup(self.config, self.stop_event)
self.event_cleanup = EventCleanup(self.config, self.stop_event, self.db)
self.event_cleanup.start()
def start_record_cleanup(self) -> None:
@@ -591,7 +493,7 @@ class FrigateApp:
self.frigate_watchdog = FrigateWatchdog(self.detectors, self.stop_event)
self.frigate_watchdog.start()
def check_shm(self) -> None:
def shm_frame_count(self) -> int:
total_shm = round(shutil.disk_usage("/dev/shm").total / pow(2, 20), 1)
# required for log files + nginx cache
@@ -601,27 +503,29 @@ class FrigateApp:
min_req_shm += 8
available_shm = total_shm - min_req_shm
cam_total_frame_size = 0
cam_total_frame_size = 0.0
for camera in self.config.cameras.values():
if camera.enabled:
if camera.enabled and camera.detect.width and camera.detect.height:
cam_total_frame_size += round(
(camera.detect.width * camera.detect.height * 1.5 + 270480)
/ 1048576,
1,
)
self.shm_frame_count = min(50, int(available_shm / (cam_total_frame_size)))
shm_frame_count = min(50, int(available_shm / (cam_total_frame_size)))
logger.debug(
f"Calculated total camera size {available_shm} / {cam_total_frame_size} :: {self.shm_frame_count} frames for each camera in SHM"
f"Calculated total camera size {available_shm} / {cam_total_frame_size} :: {shm_frame_count} frames for each camera in SHM"
)
if self.shm_frame_count < 10:
if shm_frame_count < 10:
logger.warning(
f"The current SHM size of {total_shm}MB is too small, recommend increasing it to at least {round(min_req_shm + cam_total_frame_size)}MB."
f"The current SHM size of {total_shm}MB is too small, recommend increasing it to at least {round(min_req_shm + cam_total_frame_size * 10)}MB."
)
return shm_frame_count
def init_auth(self) -> None:
if self.config.auth.enabled:
if User.select().count() == 0:
@@ -633,6 +537,7 @@ class FrigateApp:
{
User.username: "admin",
User.password_hash: password_hash,
User.notification_tokens: [],
}
).execute()
@@ -649,7 +554,11 @@ class FrigateApp:
password_hash = hash_password(
password, iterations=self.config.auth.hash_iterations
)
User.replace(username="admin", password_hash=password_hash).execute()
User.replace(
username="admin",
password_hash=password_hash,
notification_tokens=[],
).execute()
logger.info("********************************************************")
logger.info("********************************************************")
@@ -658,97 +567,64 @@ class FrigateApp:
logger.info("********************************************************")
logger.info("********************************************************")
@log_thread()
def start(self) -> None:
parser = argparse.ArgumentParser(
prog="Frigate",
description="An NVR with realtime local object detection for IP cameras.",
)
parser.add_argument("--validate-config", action="store_true")
args = parser.parse_args()
logger.info(f"Starting Frigate ({VERSION})")
try:
self.ensure_dirs()
try:
self.init_config()
except Exception as e:
print("*************************************************************")
print("*************************************************************")
print("*** Your config file is not valid! ***")
print("*** Please check the docs at ***")
print("*** https://docs.frigate.video/configuration/index ***")
print("*************************************************************")
print("*************************************************************")
print("*** Config Validation Errors ***")
print("*************************************************************")
if isinstance(e, ValidationError):
for error in e.errors():
location = ".".join(str(item) for item in error["loc"])
print(f"{location}: {error['msg']}")
else:
print(e)
print(traceback.format_exc())
print("*************************************************************")
print("*** End Config Validation Errors ***")
print("*************************************************************")
sys.exit(1)
if args.validate_config:
print("*************************************************************")
print("*** Your config file is valid. ***")
print("*************************************************************")
sys.exit(0)
self.set_environment_vars()
self.set_log_levels()
self.init_queues()
self.init_database()
self.init_onvif()
self.init_recording_manager()
self.init_review_segment_manager()
self.init_embeddings_manager()
self.init_go2rtc()
self.bind_database()
self.check_db_data_migrations()
self.init_inter_process_communicator()
self.init_dispatcher()
except Exception as e:
print(e)
sys.exit(1)
# Ensure global state.
self.ensure_dirs()
# Start frigate services.
self.init_camera_metrics()
self.init_queues()
self.init_database()
self.init_onvif()
self.init_recording_manager()
self.init_review_segment_manager()
self.init_go2rtc()
self.bind_database()
self.check_db_data_migrations()
self.init_inter_process_communicator()
self.init_dispatcher()
self.start_detectors()
self.init_embeddings_manager()
self.init_embeddings_client()
self.start_video_output_processor()
self.start_ptz_autotracker()
self.init_historical_regions()
self.start_detected_frames_processor()
self.start_camera_processors()
self.check_shm()
self.start_camera_capture_processes()
self.start_audio_processors()
self.start_audio_processor()
self.start_storage_maintainer()
self.init_external_event_processor()
self.start_stats_emitter()
self.init_web_server()
self.start_timeline_processor()
self.start_event_processor()
self.start_event_cleanup()
self.start_record_cleanup()
self.start_watchdog()
self.init_auth()
# Flask only listens for SIGINT, so we need to catch SIGTERM and send SIGINT
def receiveSignal(signalNumber: int, frame: Optional[FrameType]) -> None:
os.kill(os.getpid(), signal.SIGINT)
signal.signal(signal.SIGTERM, receiveSignal)
try:
self.flask_app.run(host="127.0.0.1", port=5001, debug=False, threaded=True)
except KeyboardInterrupt:
pass
logger.info("Flask has exited...")
self.stop()
uvicorn.run(
create_fastapi_app(
self.config,
self.db,
self.embeddings,
self.detected_frames_processor,
self.storage_maintainer,
self.onvif_controller,
self.external_event_processor,
self.stats_emitter,
self.event_metadata_updater,
),
host="127.0.0.1",
port=5001,
log_level="error",
)
finally:
self.stop()
def stop(self) -> None:
logger.info("Stopping...")
@@ -764,28 +640,27 @@ class FrigateApp:
).execute()
# stop the audio process
if self.audio_process is not None:
if self.audio_process:
self.audio_process.terminate()
self.audio_process.join()
# ensure the capture processes are done
for camera in self.camera_metrics.keys():
capture_process = self.camera_metrics[camera]["capture_process"]
for camera, metrics in self.camera_metrics.items():
capture_process = metrics.capture_process
if capture_process is not None:
logger.info(f"Waiting for capture process for {camera} to stop")
capture_process.terminate()
capture_process.join()
# ensure the camera processors are done
for camera in self.camera_metrics.keys():
camera_process = self.camera_metrics[camera]["process"]
for camera, metrics in self.camera_metrics.items():
camera_process = metrics.process
if camera_process is not None:
logger.info(f"Waiting for process for {camera} to stop")
camera_process.terminate()
camera_process.join()
logger.info(f"Closing frame queue for {camera}")
frame_queue = self.camera_metrics[camera]["frame_queue"]
empty_and_close_queue(frame_queue)
empty_and_close_queue(metrics.frame_queue)
# ensure the detectors are done
for detector in self.detectors.values():
@@ -829,6 +704,7 @@ class FrigateApp:
# Stop Communicators
self.inter_process_communicator.stop()
self.inter_config_updater.stop()
self.event_metadata_updater.stop()
self.inter_zmq_proxy.stop()
while len(self.detection_shms) > 0:

View File

@@ -0,0 +1,68 @@
import multiprocessing as mp
from multiprocessing.sharedctypes import Synchronized
from multiprocessing.synchronize import Event
from typing import Optional
class CameraMetrics:
camera_fps: Synchronized
detection_fps: Synchronized
detection_frame: Synchronized
process_fps: Synchronized
skipped_fps: Synchronized
read_start: Synchronized
audio_rms: Synchronized
audio_dBFS: Synchronized
frame_queue: mp.Queue
process: Optional[mp.Process]
capture_process: Optional[mp.Process]
ffmpeg_pid: Synchronized
def __init__(self):
self.camera_fps = mp.Value("d", 0)
self.detection_fps = mp.Value("d", 0)
self.detection_frame = mp.Value("d", 0)
self.process_fps = mp.Value("d", 0)
self.skipped_fps = mp.Value("d", 0)
self.read_start = mp.Value("d", 0)
self.audio_rms = mp.Value("d", 0)
self.audio_dBFS = mp.Value("d", 0)
self.frame_queue = mp.Queue(maxsize=2)
self.process = None
self.capture_process = None
self.ffmpeg_pid = mp.Value("i", 0)
class PTZMetrics:
autotracker_enabled: Synchronized
start_time: Synchronized
stop_time: Synchronized
frame_time: Synchronized
zoom_level: Synchronized
max_zoom: Synchronized
min_zoom: Synchronized
tracking_active: Event
motor_stopped: Event
reset: Event
def __init__(self, *, autotracker_enabled: bool):
self.autotracker_enabled = mp.Value("i", autotracker_enabled)
self.start_time = mp.Value("d", 0)
self.stop_time = mp.Value("d", 0)
self.frame_time = mp.Value("d", 0)
self.zoom_level = mp.Value("d", 0)
self.max_zoom = mp.Value("d", 0)
self.min_zoom = mp.Value("d", 0)
self.tracking_active = mp.Event()
self.motor_stopped = mp.Event()
self.reset = mp.Event()
self.motor_stopped.set()

View File

@@ -6,6 +6,7 @@ import logging
from abc import ABC, abstractmethod
from typing import Any, Callable, Optional
from frigate.camera import PTZMetrics
from frigate.comms.config_updater import ConfigPublisher
from frigate.config import BirdseyeModeEnum, FrigateConfig
from frigate.const import (
@@ -15,11 +16,12 @@ from frigate.const import (
REQUEST_REGION_GRID,
UPDATE_CAMERA_ACTIVITY,
UPDATE_EVENT_DESCRIPTION,
UPDATE_MODEL_STATE,
UPSERT_REVIEW_SEGMENT,
)
from frigate.models import Event, Previews, Recordings, ReviewSegment
from frigate.ptz.onvif import OnvifCommandEnum, OnvifController
from frigate.types import PTZMetricsTypes
from frigate.types import ModelStatusTypesEnum
from frigate.util.object import get_camera_regions_grid
from frigate.util.services import restart_frigate
@@ -53,7 +55,7 @@ class Dispatcher:
config: FrigateConfig,
config_updater: ConfigPublisher,
onvif: OnvifController,
ptz_metrics: dict[str, PTZMetricsTypes],
ptz_metrics: dict[str, PTZMetrics],
communicators: list[Communicator],
) -> None:
self.config = config
@@ -83,6 +85,7 @@ class Dispatcher:
comm.subscribe(self._receive)
self.camera_activity = {}
self.model_state = {}
def _receive(self, topic: str, payload: str) -> Optional[Any]:
"""Handle receiving of payload from communicators."""
@@ -140,6 +143,18 @@ class Dispatcher:
event: Event = Event.get(Event.id == payload["id"])
event.data["description"] = payload["description"]
event.save()
self.publish(
"event_update",
json.dumps({"id": event.id, "description": event.data["description"]}),
)
elif topic == UPDATE_MODEL_STATE:
model = payload["model"]
state = payload["state"]
self.model_state[model] = ModelStatusTypesEnum[state]
self.publish("model_state", json.dumps(self.model_state))
elif topic == "modelState":
model_state = self.model_state.copy()
self.publish("model_state", json.dumps(model_state))
elif topic == "onConnect":
camera_status = self.camera_activity.copy()
@@ -247,16 +262,16 @@ class Dispatcher:
"Autotracking must be enabled in the config to be turned on via MQTT."
)
return
if not self.ptz_metrics[camera_name]["ptz_autotracker_enabled"].value:
if not self.ptz_metrics[camera_name].autotracker_enabled.value:
logger.info(f"Turning on ptz autotracker for {camera_name}")
self.ptz_metrics[camera_name]["ptz_autotracker_enabled"].value = True
self.ptz_metrics[camera_name]["ptz_start_time"].value = 0
self.ptz_metrics[camera_name].autotracker_enabled.value = True
self.ptz_metrics[camera_name].start_time.value = 0
ptz_autotracker_settings.enabled = True
elif payload == "OFF":
if self.ptz_metrics[camera_name]["ptz_autotracker_enabled"].value:
if self.ptz_metrics[camera_name].autotracker_enabled.value:
logger.info(f"Turning off ptz autotracker for {camera_name}")
self.ptz_metrics[camera_name]["ptz_autotracker_enabled"].value = False
self.ptz_metrics[camera_name]["ptz_start_time"].value = 0
self.ptz_metrics[camera_name].autotracker_enabled.value = False
self.ptz_metrics[camera_name].start_time.value = 0
ptz_autotracker_settings.enabled = False
self.publish(f"{camera_name}/ptz_autotracker/state", payload, retain=True)

View File

@@ -0,0 +1,51 @@
"""Facilitates communication between processes."""
import logging
from enum import Enum
from typing import Optional
from frigate.events.types import RegenerateDescriptionEnum
from .zmq_proxy import Publisher, Subscriber
logger = logging.getLogger(__name__)
class EventMetadataTypeEnum(str, Enum):
all = ""
regenerate_description = "regenerate_description"
class EventMetadataPublisher(Publisher):
"""Simplifies receiving event metadata."""
topic_base = "event_metadata/"
def __init__(self, topic: EventMetadataTypeEnum) -> None:
topic = topic.value
super().__init__(topic)
def publish(self, payload: tuple[str, RegenerateDescriptionEnum]) -> None:
super().publish(payload)
class EventMetadataSubscriber(Subscriber):
"""Simplifies receiving event metadata."""
topic_base = "event_metadata/"
def __init__(self, topic: EventMetadataTypeEnum) -> None:
topic = topic.value
super().__init__(topic)
def check_for_update(
self, timeout: float = None
) -> Optional[tuple[EventMetadataTypeEnum, str, RegenerateDescriptionEnum]]:
return super().check_for_update(timeout)
def _return_object(self, topic: str, payload: any) -> any:
if payload is None:
return (None, None, None)
topic = EventMetadataTypeEnum[topic[len(self.topic_base) :]]
event_id, source = payload
return (topic, event_id, RegenerateDescriptionEnum(source))

View File

@@ -222,6 +222,10 @@ class MqttClient(Communicator): # type: ignore[misc]
self.on_mqtt_command,
)
self.client.message_callback_add(
f"{self.mqtt_config.topic_prefix}/onConnect", self.on_mqtt_command
)
self.client.message_callback_add(
f"{self.mqtt_config.topic_prefix}/restart", self.on_mqtt_command
)

View File

@@ -12,8 +12,7 @@ SOCKET_SUB = "ipc:///tmp/cache/proxy_sub"
class ZmqProxyRunner(threading.Thread):
def __init__(self, context: zmq.Context[zmq.Socket]) -> None:
threading.Thread.__init__(self)
self.name = "detection_proxy"
super().__init__(name="detection_proxy")
self.context = context
def run(self) -> None:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
from frigate.detectors import DetectorConfig, ModelConfig # noqa: F401
from .auth import * # noqa: F403
from .camera import * # noqa: F403
from .camera_group import * # noqa: F403
from .config import * # noqa: F403
from .database import * # noqa: F403
from .logger import * # noqa: F403
from .mqtt import * # noqa: F403
from .notification import * # noqa: F403
from .proxy import * # noqa: F403
from .semantic_search import * # noqa: F403
from .telemetry import * # noqa: F403
from .tls import * # noqa: F403
from .ui import * # noqa: F403

36
frigate/config/auth.py Normal file
View File

@@ -0,0 +1,36 @@
from typing import Optional
from pydantic import Field
from .base import FrigateBaseModel
__all__ = ["AuthConfig"]
class AuthConfig(FrigateBaseModel):
enabled: bool = Field(default=True, title="Enable authentication")
reset_admin_password: bool = Field(
default=False, title="Reset the admin password on startup"
)
cookie_name: str = Field(
default="frigate_token", title="Name for jwt token cookie", pattern=r"^[a-z]_*$"
)
cookie_secure: bool = Field(default=False, title="Set secure flag on cookie")
session_length: int = Field(
default=86400, title="Session length for jwt session tokens", ge=60
)
refresh_time: int = Field(
default=43200,
title="Refresh the session if it is going to expire in this many seconds",
ge=30,
)
failed_login_rate_limit: Optional[str] = Field(
default=None,
title="Rate limits for failed login attempts.",
)
trusted_proxies: list[str] = Field(
default=[],
title="Trusted proxies for determining IP address to rate limit",
)
# As of Feb 2023, OWASP recommends 600000 iterations for PBKDF2-SHA256
hash_iterations: int = Field(default=600000, title="Password hash iterations")

5
frigate/config/base.py Normal file
View File

@@ -0,0 +1,5 @@
from pydantic import BaseModel, ConfigDict
class FrigateBaseModel(BaseModel):
model_config = ConfigDict(extra="forbid", protected_namespaces=())

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