Compare commits

..

123 Commits

Author SHA1 Message Date
Blake Blackshear
bfecee9650 add missing optional comment in docs 2022-02-18 21:18:26 -06:00
Blake Blackshear
395c16300d deregister based on max_frames setting 2022-02-18 21:18:26 -06:00
Blake Blackshear
ff19cdb773 refactor stationary config into section 2022-02-18 21:18:26 -06:00
Nicolas Mowen
5627b66a6e Always show recording link even if recordings are currently disabled (#2787)
* Always show recording link even if recordings are currently disabled

* Fix test to consider all cameras to have recording link
2022-02-18 21:18:26 -06:00
Blake Blackshear
ebdfbfe96c update birdseye to handle stationary objects 2022-02-18 21:18:26 -06:00
Blake Blackshear
1a009c7fd1 use second stream in docs example 2022-02-18 21:18:26 -06:00
Blake Blackshear
c14f986fae stop forcing detection all the way to stationary_threshold 2022-02-18 21:18:26 -06:00
Blake Blackshear
ee5b9986ad bump default stationary_threshold to 10s 2022-02-18 21:18:26 -06:00
Blake Blackshear
190f217b13 set stationary_threshold default to 5x fps 2022-02-18 21:18:26 -06:00
Blake Blackshear
e78662b924 fix the bounding box calculation position at 10 2022-02-18 21:18:26 -06:00
Blake Blackshear
5a2e395352 selectively increment position changes 2022-02-18 21:18:26 -06:00
Jason Hunter
8de15af7b4 Fix duration for long events and playback rate for top of the hour 2022-02-18 21:18:26 -06:00
Jason Hunter
21178613de Only send significant update once when motionless count reaches the defined threshold. 2022-02-18 21:18:26 -06:00
Jason Hunter
340be7f86d Allow download of in progress clips 2022-02-18 21:18:26 -06:00
Blake Blackshear
0ff4acd59c remove invalid warning 2022-02-18 21:18:26 -06:00
Jason Hunter
28dd43f8ae Fix playback rate resetting to 1 on source change 2022-02-18 21:18:26 -06:00
Jason Hunter
56d24cbf6d Update package-lock.json 2022-02-18 21:18:26 -06:00
Jason Hunter
e433bec17f Add in progress events to recordings view 2022-02-18 21:18:26 -06:00
Blake Blackshear
3189467a36 update an object once per minute 2022-02-18 21:18:26 -06:00
Blake Blackshear
63536d249f signal an update when object becomes stationary 2022-02-18 21:18:26 -06:00
Blake Blackshear
3e90f3032c make stationary_threshold configurable 2022-02-18 21:18:26 -06:00
Blake Blackshear
5cff849e59 publish an update on position changes 2022-02-18 21:18:26 -06:00
Blake Blackshear
06cc7527a9 only update db entry when a stored property changes 2022-02-18 21:18:26 -06:00
Blake Blackshear
d78dc2388c increment motionless_count 2022-02-18 21:18:26 -06:00
Blake Blackshear
583912db9c allow motion based retention when detect is disabled 2022-02-18 21:18:26 -06:00
Blake Blackshear
5792cf042e fix resolution on reolink example 2022-02-18 21:18:26 -06:00
Blake Blackshear
4524dca3ed clarify addon versions 2022-02-18 21:18:26 -06:00
Blake Blackshear
304a569c7e remove outdated output args tip 2022-02-18 21:18:26 -06:00
Blake Blackshear
10c200dc24 clarify that zones are based on the bottom center 2022-02-18 21:18:26 -06:00
Blake Blackshear
0b8617d09f update addon urls 2022-02-18 21:18:26 -06:00
Blake Blackshear
e7026dfd6e add example for ios camera live feed notification 2022-02-18 21:18:26 -06:00
Blake Blackshear
b82d75b79e avoid rare divide by zero 2022-02-18 21:18:26 -06:00
Blake Blackshear
ac30091258 note for future 2022-02-18 21:18:26 -06:00
Blake Blackshear
de58bdcc9f improve warning for retain modes 2022-02-18 21:18:26 -06:00
Blake Blackshear
329e5f8f91 invert active_count logic 2022-02-18 21:18:26 -06:00
Blake Blackshear
4a16171f96 set has_clip to false when recordings fail 2022-02-18 21:18:26 -06:00
Blake Blackshear
f0212c2aa4 adjust error messages on ffmpeg crash 2022-02-18 21:18:26 -06:00
Blake Blackshear
7401cf2399 add stacktrace to config validation errors 2022-02-18 21:18:26 -06:00
Blake Blackshear
493d16519a add new properties to the docs 2022-02-18 21:18:26 -06:00
Blake Blackshear
a87675d3cc add additional info for non-H264 cameras 2022-02-18 21:18:26 -06:00
Blake Blackshear
0bef16bb17 upgrade npm in dev container 2022-02-18 21:18:26 -06:00
Blake Blackshear
e0078f388e package updates for docs 2022-02-18 21:18:26 -06:00
Blake Blackshear
cf6e66c453 allow dash in camera name 2022-02-18 21:18:26 -06:00
Blake Blackshear
34fa53afcc make motion the default retain mode 2022-02-18 21:18:26 -06:00
Blake Blackshear
663bf05fd7 update stationary interval docs 2022-02-18 21:18:26 -06:00
Blake Blackshear
f512af2563 make expire interval configurable for users wanting to minimize i/o 2022-02-18 21:18:26 -06:00
Blake Blackshear
7e7d70aa5b avoid extra tracking work on stationary frames 2022-02-18 21:18:26 -06:00
Blake Blackshear
dd1cf4d2ce use iou instead of centroid 2022-02-18 21:18:26 -06:00
Blake Blackshear
e627f4e935 dont stop scanning when there are other regions 2022-02-18 21:18:26 -06:00
Blake Blackshear
c6445898ce default periodic checks to never 2022-02-18 21:18:26 -06:00
Blake Blackshear
f1ddd0e6f7 scan the frame on startup 2022-02-18 21:18:26 -06:00
Blake Blackshear
db369a5b7f require a position change to be an active object 2022-02-18 21:18:26 -06:00
Blake Blackshear
87cd618998 randomize the region multiplier for variation 2022-02-18 21:18:26 -06:00
Blake Blackshear
338e4004d4 improve method for determining position
compares the centroid to a history of bounding boxes
2022-02-18 21:18:26 -06:00
Blake Blackshear
675f21e23a if recording not on disk, delete from db and return 2022-02-18 21:18:26 -06:00
Blake Blackshear
4d2d11193f cleanup clean snapshots on event deletion too 2022-02-18 21:18:26 -06:00
Blake Blackshear
69aaf1f8e6 require url safe camera names 2022-02-18 21:18:26 -06:00
Bernt Christian Egeland
a10970d7c9 Event Datepicker (#2428)
* new datepicker

* dev

* dev

* dev

* fix for version 0.10

* added rounded corners for date range

* lint

* Commented out some Select.test.

* improved date range selection

* improved functions with useCallback

* improved Select.test.jsx

* keyboard navigation

* keyboard navigation

* added dropdown menu icon

* Hide filters on xs, Button to show

* check if to far left before right

* Filter button text

* improved local timezone
2022-02-18 21:18:26 -06:00
Yuriy Sannikov
6eecb6780e Run python unit tests in a github actions (#2589)
* tox tests initial commit

* run tests in the Dockerfile during the build phase

* remove local tests

Co-authored-by: YS <ys@gm.com>
2022-02-18 21:18:26 -06:00
Yuriy Sannikov
80627e4989 safe refactoring (#2552)
Co-authored-by: YS <ys@gm.com>
2022-02-18 21:18:26 -06:00
TJ Horner
9e987fdebc Change JPEG mime type (#2543) 2022-02-18 21:18:26 -06:00
Blake Blackshear
e6292c719d disable disk sync on startup 2022-02-18 21:18:26 -06:00
Blake Blackshear
7c74bf2566 fix migrations 2022-02-18 21:18:26 -06:00
Blake Blackshear
2c91e7853c check for apex dir 2022-02-18 21:18:26 -06:00
Ryan McLean
1e7f196e5c #2117 change entered_zones from set to list so that they are not automatically alphabetically ordered (#2212) 2022-02-18 21:18:26 -06:00
Justin Goette
f91f4f0053 Allow for ".yaml" (#2244)
* allow for ".yaml"

* remove unused import
2022-02-18 21:18:26 -06:00
Matt Clayton
8b2622a234 Add temperature of coral tpu to telemetry mqtt message 2022-02-18 21:18:26 -06:00
Blake Blackshear
a2ddb12eb3 limit vod response cache 2022-02-18 21:18:26 -06:00
Blake Blackshear
985bd6d9bd update docs 2022-02-18 21:18:26 -06:00
Blake Blackshear
ec3c15e4a7 expire overlapping segments based on mode 2022-02-18 21:18:26 -06:00
Blake Blackshear
188b202836 store objects and motion counts in the db 2022-02-18 21:18:26 -06:00
Blake Blackshear
01e607a14e warn when retention mismatch 2022-02-18 21:18:26 -06:00
Blake Blackshear
5b164b72dc refactor segment stats logic 2022-02-18 21:18:26 -06:00
Blake Blackshear
dcf65febba switch to retain config instead of retain_days 2022-02-18 21:18:26 -06:00
Blake Blackshear
56a2d4e64d pass processed tracked objects 2022-02-18 21:18:26 -06:00
Blake Blackshear
ef214fb80a retain frame data for recording maintenance 2022-02-18 21:18:26 -06:00
Blake Blackshear
93f418ac0b fix process_clip 2022-02-18 21:18:26 -06:00
Blake Blackshear
689af4ff87 sync recordings with disk once on startup 2022-02-18 21:18:26 -06:00
Blake Blackshear
4ab0927de8 no need to expire recordings every minute 2022-02-18 21:18:26 -06:00
Blake Blackshear
014e6fc909 ensure cache copies when events have ended 2022-02-18 21:18:26 -06:00
Blake Blackshear
6832575643 cleanup missing files from database once per hour 2022-02-18 21:18:26 -06:00
Blake Blackshear
07ad2d97b1 handle missing file edge case 2022-02-18 21:18:26 -06:00
Blake Blackshear
039f1a522e log error messages on vod endpoints 2022-02-18 21:18:26 -06:00
Blake Blackshear
24e2f84231 ensure duration > 0 for segments 2022-02-18 21:18:26 -06:00
Blake Blackshear
e0c0033852 use snapshot url to support in progress events 2022-02-18 21:18:26 -06:00
Blake Blackshear
c50e9d48bf ensure stationary interval is greater than 0 2022-02-18 21:18:26 -06:00
Blake Blackshear
173eaabddf add duration to cache 2022-02-18 21:18:26 -06:00
Blake Blackshear
a748b70da1 avoid running ffprobe for each segment multiple times 2022-02-18 21:18:26 -06:00
Blake Blackshear
8eabe5dd41 warn if no wait time 2022-02-18 21:18:26 -06:00
Blake Blackshear
114415b5e1 keep 5 segments in cache 2022-02-18 21:18:26 -06:00
Blake Blackshear
ba55b5a6db better cache handling 2022-02-18 21:18:26 -06:00
Blake Blackshear
7533f2a8ab avoid proactive messages with retain_days 0 and handle first pass 2022-02-18 21:18:26 -06:00
Blake Blackshear
543a8a1712 avoid divide by zero 2022-02-18 21:18:26 -06:00
Blake Blackshear
9b23ff597c revert switch to b/w frame prep 2022-02-18 21:18:26 -06:00
Blake Blackshear
b2ce1edd5a fix default motion comment 2022-02-18 21:18:26 -06:00
Blake Blackshear
a0235b7da4 more robust cache management 2022-02-18 21:18:26 -06:00
Blake Blackshear
87e2300855 set retain when setting switches from frontend 2022-02-18 21:18:26 -06:00
Blake Blackshear
34bc6a6457 error handling for the recording maintainer 2022-02-18 21:18:26 -06:00
Blake Blackshear
273076e7f4 don't modify ffmpeg_cmd object 2022-02-18 21:18:26 -06:00
Blake Blackshear
b29b311e92 fix ffmpeg config for env vars 2022-02-18 21:18:26 -06:00
Blake Blackshear
5a9e82c4b0 create ffmpeg commands on startup 2022-02-18 21:18:26 -06:00
Blake Blackshear
6218791708 clarify shm in docs 2022-02-18 21:18:26 -06:00
Blake Blackshear
0e43f452d2 use resolution of clip 2022-02-18 21:18:26 -06:00
Blake Blackshear
0695bb097d revamp process clip 2022-02-18 21:18:26 -06:00
Blake Blackshear
294c79a271 no longer make motion settings dynamic 2022-02-18 21:18:26 -06:00
Blake Blackshear
e351e132f5 remove min frame height of 180 and increase contour area 2022-02-18 21:18:26 -06:00
Blake Blackshear
258215a3ae consolidate regions 2022-02-18 21:18:26 -06:00
Blake Blackshear
08ddfc100f improve contrast 2022-02-18 21:18:26 -06:00
Blake Blackshear
8ab6cba521 check for overlapping motion boxes 2022-02-18 21:18:26 -06:00
Blake Blackshear
eb16de7395 config option for stationary detection interval 2022-02-18 21:18:26 -06:00
Blake Blackshear
dde0498ed3 drop high overlap detections 2022-02-18 21:18:26 -06:00
Blake Blackshear
75c8570913 reduce detection rate for stationary objects 2022-02-18 21:18:26 -06:00
Blake Blackshear
e36099a342 improve box merging and keep tracking 2022-02-18 21:18:26 -06:00
Blake Blackshear
2f2329ba44 only save recordings when an event is in progress 2022-02-18 21:18:26 -06:00
Blake Blackshear
6c8b184d2c version tick 2022-02-18 21:18:26 -06:00
DataBitz
32878bd016 Another missing slash (#2803)
2nd attempt to fix link to full configuration
2022-02-14 07:33:29 -06:00
DataBitz
12d13988c4 Missing slash in url (#2797)
Missing slash in url to full-configuration-reference
2022-02-13 07:38:24 -06:00
atinsley
9e4d921488 Update advanced.md (#2794)
Add details about how to specify a custom database location in config.yml
2022-02-12 06:28:56 -06:00
Blake Blackshear
edc1884c4e add warning to storage docs 2022-02-11 06:15:15 -06:00
Alex Yao
a2d1bd2c67 Document JPEG streams (#2586)
* Document JPEG streams

* Update camera_specific.md
2022-02-02 07:27:22 -06:00
Felipe Santos
bb68a2405b Improve audio conversion tip (#2140)
* Improve audio convert guide

* Mention faq in RTMP configuration

* Add example for audio conversion tip

* Change comma to period

* Explain why this is needed
2021-12-29 08:57:32 -06:00
MrNorm
42ac4172ff Add passthrough information for PCIe Coral TPU (#2200) 2021-12-12 09:31:52 -06:00
hcooper
998921ae63 Update objects.mdx
Mention that `person` is the only tracked object by default. Minor reformat.
2021-12-01 07:33:16 -06:00
22 changed files with 15058 additions and 95 deletions

View File

@@ -43,6 +43,11 @@ If you are storing your database on a network share (SMB, NFS, etc), you may get
This may need to be in a custom location if network storage is used for the media folder.
```yaml
database:
path: /path/to/frigate.db
```
### `model`
If using a custom model, the width and height will need to be specified.

View File

@@ -19,6 +19,34 @@ output_args:
rtmp: -c:v libx264 -an -f flv
```
### JPEG Stream Cameras
Cameras using a live changing jpeg image will need input parameters as below
```yaml
input_args:
- -r
- 5 # << enter FPS here
- -stream_loop
- -1
- -f
- image2
- -avoid_negative_ts
- make_zero
- -fflags
- nobuffer
- -flags
- low_delay
- -strict
- experimental
- -fflags
- +genpts+discardcorrupt
- -use_wallclock_as_timestamps
- 1
```
Outputting the stream will have the same args and caveats as per [MJPEG Cameras](#mjpeg-cameras)
### RTMP Cameras
The input parameters need to be adjusted for RTMP cameras

View File

@@ -159,9 +159,23 @@ detect:
enabled: True
# Optional: Number of frames without a detection before frigate considers an object to be gone. (default: 5x the frame rate)
max_disappeared: 25
# Optional: Frequency for running detection on stationary objects (default: 0)
# When set to 0, object detection will never be run on stationary objects. If set to 10, it will be run on every 10th frame.
stationary_interval: 0
# Optional: Configuration for stationary object tracking
stationary:
# Optional: Frequency for running detection on stationary objects (default: shown below)
# When set to 0, object detection will never be run on stationary objects. If set to 10, it will be run on every 10th frame.
interval: 0
# Optional: Number of frames without a position change for an object to be considered stationary (default: 10x the frame rate or 10s)
threshold: 50
# Optional: Define a maximum number of frames for tracking a stationary object (default: not set, track forever)
# This can help with false positives for objects that should only be stationary for a limited amount of time.
# It can also be used to disable stationary object tracking. For example, you may want to set a value for person, but leave
# car at the default.
max_frames:
# Optional: Default for all object types (default: not set, track forever)
default: 3000
# Optional: Object specific values
objects:
person: 1000
# Optional: Object configuration
# NOTE: Can be overridden at the camera level
@@ -224,6 +238,11 @@ motion:
# NOTE: Can be overridden at the camera level
record:
# Optional: Enable recording (default: shown below)
# WARNING: Frigate does not currently support limiting recordings based
# on available disk space automatically. If using recordings,
# you must specify retention settings for a number of days that
# will fit within the available disk space of your drive or Frigate
# will crash.
enabled: False
# Optional: Number of minutes to wait between cleanup runs (default: shown below)
# This can be used to reduce the frequency of deleting recording segments from disk if you want to minimize i/o

View File

@@ -5,7 +5,11 @@ title: Objects
import labels from "../../../labelmap.txt";
By default, Frigate includes the following object models from the Google Coral test data. Note that `car` is listed twice because `truck` has been renamed to `car` by default. These object types are frequently confused.
Frigate includes the object models listed below from the Google Coral test data.
Please note:
- `car` is listed twice because `truck` has been renamed to `car` by default. These object types are frequently confused.
- `person` is the only tracked object by default. See the [full configuration reference](https://docs.frigate.video/configuration/index#full-configuration-reference) for an example of expanding the list of tracked objects.
<ul>
{labels.split("\n").map((label) => (

View File

@@ -5,4 +5,4 @@ title: RTMP
Frigate can re-stream your video feed as a RTMP feed for other applications such as Home Assistant to utilize it at `rtmp://<frigate_host>/live/<camera_name>`. Port 1935 must be open. This allows you to use a video feed for detection in frigate and Home Assistant live view at the same time without having to make two separate connections to the camera. The video feed is copied from the original video feed directly to avoid re-encoding. This feed does not include any annotation by Frigate.
Some video feeds are not compatible with RTMP. If you are experiencing issues, check to make sure your camera feed is h264 with AAC audio. If your camera doesn't support a compatible format for RTMP, you can use the ffmpeg args to re-encode it on the fly at the expense of increased CPU utilization.
Some video feeds are not compatible with RTMP. If you are experiencing issues, check to make sure your camera feed is h264 with AAC audio. If your camera doesn't support a compatible format for RTMP, you can use the ffmpeg args to re-encode it on the fly at the expense of increased CPU utilization. Some more information about it can be found [here](../faqs#audio-in-recordings).

View File

@@ -11,9 +11,24 @@ This error message is due to a shm-size that is too small. Try updating your shm
A solid green image means that frigate has not received any frames from ffmpeg. Check the logs to see why ffmpeg is exiting and adjust your ffmpeg args accordingly.
### How can I get sound or audio in my recordings?
### How can I get sound or audio in my recordings? {#audio-in-recordings}
By default, Frigate removes audio from recordings to reduce the likelihood of failing for invalid data. If you would like to include audio, you need to override the output args to remove `-an` for where you want to include audio. The recommended audio codec is `aac`. Not all audio codecs are supported by RTMP, so you may need to re-encode your audio with `-c:a aac`. The default ffmpeg args are shown [here](configuration/index#full-configuration-reference).
By default, Frigate removes audio from recordings to reduce the likelihood of failing for invalid data. If you would like to include audio, you need to override the output args to remove `-an` for where you want to include audio. The recommended audio codec is `aac`. Not all audio codecs are supported by RTMP, so you may need to re-encode your audio with `-c:a aac`. The default ffmpeg args are shown [here](/configuration/index/#full-configuration-reference).
:::tip
When using `-c:a aac`, do not forget to replace `-c copy` with `-c:v copy`. Example:
```diff title="frigate.yml"
ffmpeg:
output_args:
- record: -f segment -segment_time 10 -segment_format mp4 -reset_timestamps 1 -strftime 1 -c copy -an
+ record: -f segment -segment_time 10 -segment_format mp4 -reset_timestamps 1 -strftime 1 -c:v copy -c:a aac
```
This is needed because the `-c` flag (without `:a` or `:v`) applies for both audio and video, thus making it conflicting with `-c:a aac`.
:::
### My mjpeg stream or snapshots look green and crazy

View File

@@ -167,13 +167,17 @@ cameras:
roles:
- detect
- rtmp
- record # <----- Add role
- path: rtsp://10.0.10.10:554/high_res_stream # <----- Add high res stream
roles:
- record
detect: ...
record: # <----- Enable recording
enabled: True
motion: ...
```
If you don't have separate streams for detect and record, you would just add the record role to the list on the first input.
By default, Frigate will retain video of all events for 10 days. The full set of options for recording can be found [here](/configuration/index#full-configuration-reference).
### Step 8: Enable snapshots (optional)

View File

@@ -21,6 +21,12 @@ Windows is not officially supported, but some users have had success getting it
Frigate uses the following locations for read/write operations in the container. Docker volume mappings can be used to map these to any location on your host machine.
:::caution
Note that Frigate does not currently support limiting recordings based on available disk space automatically. If using recordings, you must specify retention settings for a number of days that will fit within the available disk space of your drive or Frigate will crash.
:::
- `/media/frigate/clips`: Used for snapshot storage. In the future, it will likely be renamed from `clips` to `snapshots`. The file structure here cannot be modified and isn't intended to be browsed or managed manually.
- `/media/frigate/recordings`: Internal system storage for recording segments. The file structure here cannot be modified and isn't intended to be browsed or managed manually.
- `/media/frigate/frigate.db`: Default location for the sqlite database. You will also see several files alongside this file while frigate is running. If moving the database location (often needed when using a network drive at `/media/frigate`), it is recommended to mount a volume with docker at `/db` and change the storage location of the database to `/db/frigate.db` in the config file.
@@ -118,6 +124,7 @@ services:
shm_size: "64mb" # update for your cameras based on calculation above
devices:
- /dev/bus/usb:/dev/bus/usb # passes the USB Coral, needs to be modified for other versions
- /dev/apex_0:/dev/apex_0 # passes a PCIe Coral, follow driver instructions here https://coral.ai/docs/m2/get-started/#2a-on-linux
- /dev/dri/renderD128 # for intel hwaccel, needs to be updated for your hardware
volumes:
- /etc/localtime:/etc/localtime:ro

View File

@@ -56,8 +56,9 @@ Message published for each changed event. The first message is published when th
"thumbnail": null,
"has_snapshot": false,
"has_clip": false,
"stationary": false, // whether or not the object is considered stationary
"motionless_count": 0, // number of frames the object has been motionless
"position_changes": 2 // number of times the object has changed position
"position_changes": 2 // number of times the object has moved from a stationary position
},
"after": {
"id": "1607123955.475377-mxklsc",
@@ -78,6 +79,7 @@ Message published for each changed event. The first message is published when th
"thumbnail": null,
"has_snapshot": false,
"has_clip": false,
"stationary": false, // whether or not the object is considered stationary
"motionless_count": 0, // number of frames the object has been motionless
"position_changes": 2 // number of times the object has changed position
}

View File

@@ -162,6 +162,29 @@ class RuntimeMotionConfig(MotionConfig):
extra = Extra.ignore
class StationaryMaxFramesConfig(FrigateBaseModel):
default: Optional[int] = Field(title="Default max frames.", ge=1)
objects: Dict[str, int] = Field(
default_factory=dict, title="Object specific max frames."
)
class StationaryConfig(FrigateBaseModel):
interval: Optional[int] = Field(
default=0,
title="Frame interval for checking stationary objects.",
ge=0,
)
threshold: Optional[int] = Field(
title="Number of frames without a position change for an object to be considered stationary",
ge=1,
)
max_frames: StationaryMaxFramesConfig = Field(
default_factory=StationaryMaxFramesConfig,
title="Max frames for stationary objects.",
)
class DetectConfig(FrigateBaseModel):
height: int = Field(default=720, title="Height of the stream for the detect role.")
width: int = Field(default=1280, title="Width of the stream for the detect role.")
@@ -172,10 +195,9 @@ class DetectConfig(FrigateBaseModel):
max_disappeared: Optional[int] = Field(
title="Maximum number of frames the object can dissapear before detection ends."
)
stationary_interval: Optional[int] = Field(
default=0,
title="Frame interval for checking stationary objects.",
ge=0,
stationary: StationaryConfig = Field(
default_factory=StationaryConfig,
title="Stationary objects config.",
)
@@ -766,6 +788,11 @@ class FrigateConfig(FrigateBaseModel):
if camera_config.detect.max_disappeared is None:
camera_config.detect.max_disappeared = max_disappeared
# Default stationary_threshold configuration
stationary_threshold = camera_config.detect.fps * 10
if camera_config.detect.stationary.threshold is None:
camera_config.detect.stationary.threshold = stationary_threshold
# FFMPEG input substitution
for input in camera_config.ffmpeg.inputs:
input.path = input.path.format(**FRIGATE_ENV_VARS)

View File

@@ -15,6 +15,16 @@ from frigate.models import Event
logger = logging.getLogger(__name__)
def should_update_db(prev_event, current_event):
return (
prev_event["top_score"] != current_event["top_score"]
or prev_event["entered_zones"] != current_event["entered_zones"]
or prev_event["thumbnail"] != current_event["thumbnail"]
or prev_event["has_clip"] != current_event["has_clip"]
or prev_event["has_snapshot"] != current_event["has_snapshot"]
)
class EventProcessor(threading.Thread):
def __init__(
self, config, camera_processes, event_queue, event_processed_queue, stop_event
@@ -48,7 +58,9 @@ class EventProcessor(threading.Thread):
if event_type == "start":
self.events_in_process[event_data["id"]] = event_data
elif event_type == "update":
elif event_type == "update" and should_update_db(
self.events_in_process[event_data["id"]], event_data
):
self.events_in_process[event_data["id"]] = event_data
# TODO: this will generate a lot of db activity possibly
if event_data["has_clip"] or event_data["has_snapshot"]:

View File

@@ -249,7 +249,10 @@ def event_clip(id):
clip_path = os.path.join(CLIPS_DIR, file_name)
if not os.path.isfile(clip_path):
return recording_clip(event.camera, event.start_time, event.end_time)
end_ts = (
datetime.now().timestamp() if event.end_time is None else event.end_time
)
return recording_clip(event.camera, event.start_time, end_ts)
response = make_response()
response.headers["Content-Description"] = "File Transfer"
@@ -524,12 +527,17 @@ def recordings(camera_name):
FROM C2
WHERE cnt = 0
)
SELECT id, label, camera, top_score, start_time, end_time
FROM event
WHERE camera = ? AND end_time IS NULL
UNION ALL
SELECT MIN(id) as id, label, camera, MAX(top_score) as top_score, MIN(ts) AS start_time, max(ts) AS end_time
FROM C3
GROUP BY label, grpnum
ORDER BY start_time;""",
camera_name,
camera_name,
camera_name,
)
event: Event

View File

@@ -101,14 +101,13 @@ class TrackedObject:
return median(scores)
def update(self, current_frame_time, obj_data):
significant_update = False
zone_change = False
self.obj_data.update(obj_data)
thumb_update = False
significant_change = False
# if the object is not in the current frame, add a 0.0 to the score history
if self.obj_data["frame_time"] != current_frame_time:
if obj_data["frame_time"] != current_frame_time:
self.score_history.append(0.0)
else:
self.score_history.append(self.obj_data["score"])
self.score_history.append(obj_data["score"])
# only keep the last 10 scores
if len(self.score_history) > 10:
self.score_history = self.score_history[-10:]
@@ -122,24 +121,24 @@ class TrackedObject:
if not self.false_positive:
# determine if this frame is a better thumbnail
if self.thumbnail_data is None or is_better_thumbnail(
self.thumbnail_data, self.obj_data, self.camera_config.frame_shape
self.thumbnail_data, obj_data, self.camera_config.frame_shape
):
self.thumbnail_data = {
"frame_time": self.obj_data["frame_time"],
"box": self.obj_data["box"],
"area": self.obj_data["area"],
"region": self.obj_data["region"],
"score": self.obj_data["score"],
"frame_time": obj_data["frame_time"],
"box": obj_data["box"],
"area": obj_data["area"],
"region": obj_data["region"],
"score": obj_data["score"],
}
significant_update = True
thumb_update = True
# check zones
current_zones = []
bottom_center = (self.obj_data["centroid"][0], self.obj_data["box"][3])
bottom_center = (obj_data["centroid"][0], obj_data["box"][3])
# check each zone
for name, zone in self.camera_config.zones.items():
# if the zone is not for this object type, skip
if len(zone.objects) > 0 and not self.obj_data["label"] in zone.objects:
if len(zone.objects) > 0 and not obj_data["label"] in zone.objects:
continue
contour = zone.contour
# check if the object is in the zone
@@ -150,12 +149,29 @@ class TrackedObject:
if name not in self.entered_zones:
self.entered_zones.append(name)
# if the zones changed, signal an update
if not self.false_positive and set(self.current_zones) != set(current_zones):
zone_change = True
if not self.false_positive:
# if the zones changed, signal an update
if set(self.current_zones) != set(current_zones):
significant_change = True
# if the position changed, signal an update
if self.obj_data["position_changes"] != obj_data["position_changes"]:
significant_change = True
# if the motionless_count reaches the stationary threshold
if (
self.obj_data["motionless_count"]
== self.camera_config.detect.stationary.threshold
):
significant_change = True
# update at least once per minute
if self.obj_data["frame_time"] - self.previous["frame_time"] > 60:
significant_change = True
self.obj_data.update(obj_data)
self.current_zones = current_zones
return (significant_update, zone_change)
return (thumb_update, significant_change)
def to_dict(self, include_thumbnail: bool = False):
snapshot_time = (
@@ -177,6 +193,8 @@ class TrackedObject:
"box": self.obj_data["box"],
"area": self.obj_data["area"],
"region": self.obj_data["region"],
"stationary": self.obj_data["motionless_count"]
> self.camera_config.detect.stationary.threshold,
"motionless_count": self.obj_data["motionless_count"],
"position_changes": self.obj_data["position_changes"],
"current_zones": self.current_zones.copy(),
@@ -466,11 +484,11 @@ class CameraState:
for id in updated_ids:
updated_obj = tracked_objects[id]
significant_update, zone_change = updated_obj.update(
thumb_update, significant_update = updated_obj.update(
frame_time, current_detections[id]
)
if significant_update:
if thumb_update:
# ensure this frame is stored in the cache
if (
updated_obj.thumbnail_data["frame_time"] == frame_time
@@ -480,13 +498,13 @@ class CameraState:
updated_obj.last_updated = frame_time
# if it has been more than 5 seconds since the last publish
# if it has been more than 5 seconds since the last thumb update
# and the last update is greater than the last publish or
# the object has changed zones
# the object has changed significantly
if (
frame_time - updated_obj.last_published > 5
and updated_obj.last_updated > updated_obj.last_published
) or zone_change:
) or significant_update:
# call event handlers
for c in self.callbacks["update"]:
c(self.name, updated_obj, frame_time)

View File

@@ -48,7 +48,7 @@ class ObjectTracker:
del self.tracked_objects[id]
del self.disappeared[id]
# tracks the current position of the object based on the last 10 bounding boxes
# tracks the current position of the object based on the last N bounding boxes
# returns False if the object has moved outside its previous position
def update_position(self, id, box):
position = self.positions[id]
@@ -93,20 +93,52 @@ class ObjectTracker:
return True
def is_expired(self, id):
obj = self.tracked_objects[id]
# get the max frames for this label type or the default
max_frames = self.detect_config.stationary.max_frames.objects.get(
obj["label"], self.detect_config.stationary.max_frames.default
)
# if there is no max_frames for this label type, continue
if max_frames is None:
return False
# if the object has exceeded the max_frames setting, deregister
if (
obj["motionless_count"] - self.detect_config.stationary.threshold
> max_frames
):
print(f"expired: {obj['motionless_count']}")
return True
def update(self, id, new_obj):
self.disappeared[id] = 0
# update the motionless count if the object has not moved to a new position
if self.update_position(id, new_obj["box"]):
self.tracked_objects[id]["motionless_count"] += 1
if self.is_expired(id):
self.deregister(id)
return
else:
# register the first position change and then only increment if
# the object was previously stationary
if (
self.tracked_objects[id]["position_changes"] == 0
or self.tracked_objects[id]["motionless_count"]
>= self.detect_config.stationary.threshold
):
self.tracked_objects[id]["position_changes"] += 1
self.tracked_objects[id]["motionless_count"] = 0
self.tracked_objects[id]["position_changes"] += 1
self.tracked_objects[id].update(new_obj)
def update_frame_times(self, frame_time):
for id in self.tracked_objects.keys():
for id in list(self.tracked_objects.keys()):
self.tracked_objects[id]["frame_time"] = frame_time
self.tracked_objects[id]["motionless_count"] += 1
if self.is_expired(id):
self.deregister(id)
def match_and_update(self, frame_time, new_objects):
# group by name

View File

@@ -184,10 +184,7 @@ class BirdsEyeFrameManager:
if self.mode == BirdseyeModeEnum.continuous:
return True
if (
self.mode == BirdseyeModeEnum.motion
and object_box_count + motion_box_count > 0
):
if self.mode == BirdseyeModeEnum.motion and motion_box_count > 0:
return True
if self.mode == BirdseyeModeEnum.objects and object_box_count > 0:
@@ -418,7 +415,7 @@ def output_frames(config: FrigateConfig, video_output_queue):
):
if birdseye_manager.update(
camera,
len(current_tracked_objects),
len([o for o in current_tracked_objects if not o["stationary"]]),
len(motion_boxes),
frame_time,
frame,

View File

@@ -51,7 +51,6 @@ class RecordingMaintainer(threading.Thread):
self.config = config
self.recordings_info_queue = recordings_info_queue
self.stop_event = stop_event
self.first_pass = True
self.recordings_info = defaultdict(list)
self.end_time_cache = {}
@@ -334,12 +333,6 @@ class RecordingMaintainer(threading.Thread):
logger.error(e)
duration = datetime.datetime.now().timestamp() - run_start
wait_time = max(0, 5 - duration)
if wait_time == 0 and not self.first_pass:
logger.warning(
"Cache is taking longer than 5 seconds to clear. Your recordings disk may be too slow."
)
if self.first_pass:
self.first_pass = False
logger.info(f"Exiting recording maintenance...")

View File

@@ -511,8 +511,8 @@ def process_frames(
if obj["motionless_count"] >= 10
# and it isn't due for a periodic check
and (
detect_config.stationary_interval == 0
or obj["motionless_count"] % detect_config.stationary_interval != 0
detect_config.stationary.interval == 0
or obj["motionless_count"] % detect_config.stationary.interval != 0
)
# and it hasn't disappeared
and object_tracker.disappeared[obj["id"]] == 0

14794
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,14 @@
import { h } from 'preact';
import { useState } from 'preact/hooks';
import { addSeconds, differenceInSeconds, fromUnixTime, format, parseISO, startOfHour } from 'date-fns';
import {
differenceInSeconds,
fromUnixTime,
format,
parseISO,
startOfHour,
differenceInMinutes,
differenceInHours,
} from 'date-fns';
import ArrowDropdown from '../icons/ArrowDropdown';
import ArrowDropup from '../icons/ArrowDropup';
import Link from '../components/Link';
@@ -21,25 +29,31 @@ export default function RecordingPlaylist({ camera, recordings, selectedDate, se
events={recording.events}
selected={recording.date === selectedDate}
>
{recording.recordings.slice().reverse().map((item, i) => (
<div className="mb-2 w-full">
<div
className={`flex w-full text-md text-white px-8 py-2 mb-2 ${
i === 0 ? 'border-t border-white border-opacity-50' : ''
}`}
>
<div className="flex-1">
<Link href={`/recording/${camera}/${recording.date}/${item.hour}`} type="text">
{item.hour}:00
</Link>
{recording.recordings
.slice()
.reverse()
.map((item, i) => (
<div className="mb-2 w-full">
<div
className={`flex w-full text-md text-white px-8 py-2 mb-2 ${
i === 0 ? 'border-t border-white border-opacity-50' : ''
}`}
>
<div className="flex-1">
<Link href={`/recording/${camera}/${recording.date}/${item.hour}`} type="text">
{item.hour}:00
</Link>
</div>
<div className="flex-1 text-right">{item.events.length} Events</div>
</div>
<div className="flex-1 text-right">{item.events.length} Events</div>
{item.events
.slice()
.reverse()
.map((event) => (
<EventCard camera={camera} event={event} delay={item.delay} />
))}
</div>
{item.events.slice().reverse().map((event) => (
<EventCard camera={camera} event={event} delay={item.delay} />
))}
</div>
))}
))}
</ExpandableList>
);
}
@@ -83,8 +97,17 @@ export function ExpandableList({ title, events = 0, children, selected = false }
export function EventCard({ camera, event, delay }) {
const apiHost = useApiHost();
const start = fromUnixTime(event.start_time);
const end = fromUnixTime(event.end_time);
const duration = addSeconds(new Date(0), differenceInSeconds(end, start));
let duration = 'In Progress';
if (event.end_time) {
const end = fromUnixTime(event.end_time);
const hours = differenceInHours(end, start);
const minutes = differenceInMinutes(end, start) - hours * 60;
const seconds = differenceInSeconds(end, start) - hours * 60 - minutes * 60;
duration = '';
if (hours) duration += `${hours}h `;
if (minutes) duration += `${minutes}m `;
duration += `${seconds}s`;
}
const position = differenceInSeconds(start, startOfHour(start));
const offset = Object.entries(delay)
.map(([p, d]) => (position > p ? d : 0))
@@ -102,7 +125,7 @@ export function EventCard({ camera, event, delay }) {
<div className="flex-1">
<div className="text-2xl text-white leading-tight capitalize">{event.label}</div>
<div className="text-xs md:text-normal text-gray-300">Start: {format(start, 'HH:mm:ss')}</div>
<div className="text-xs md:text-normal text-gray-300">Duration: {format(duration, 'mm:ss')}</div>
<div className="text-xs md:text-normal text-gray-300">Duration: {duration}</div>
</div>
<div className="text-lg text-white text-right leading-tight">{(event.top_score * 100).toFixed(1)}%</div>
</div>

View File

@@ -29,12 +29,8 @@ function Camera({ name, conf }) {
const { payload: snapshotValue, send: sendSnapshots } = useSnapshotsState(name);
const href = `/cameras/${name}`;
const buttons = useMemo(() => {
const result = [{ name: 'Events', href: `/events?camera=${name}` }];
if (conf.record.enabled) {
result.push({ name: 'Recordings', href: `/recording/${name}` });
}
return result;
}, [name, conf.record.enabled]);
return [{ name: 'Events', href: `/events?camera=${name}` }, { name: 'Recordings', href: `/recording/${name}` }];
}, [name]);
const icons = useMemo(
() => [
{

View File

@@ -66,6 +66,9 @@ export default function Recording({ camera, date, hour, seconds }) {
this.player.currentTime(seconds);
}
}
// Force playback rate to be correct
const playbackRate = this.player.playbackRate();
this.player.defaultPlaybackRate(playbackRate);
}
return (

View File

@@ -46,7 +46,7 @@ describe('Cameras Route', () => {
expect(screen.queryByLabelText('Loading…')).not.toBeInTheDocument();
expect(screen.queryAllByText('Recordings')).toHaveLength(1);
expect(screen.queryAllByText('Recordings')).toHaveLength(2);
});
test('buttons toggle detect, clips, and snapshots', async () => {