Compare commits

...

35 Commits

Author SHA1 Message Date
Blake Blackshear
c492b30adb Merge pull request #825 from blakeblackshear/release-0.9.0
Release 0.9.0
2021-10-05 17:59:25 -05:00
Kevin Pelzel
eb48722126 added white background to apple-touch-icon 2021-10-05 17:37:18 -05:00
Blake Blackshear
8e881b60f0 update hardware recommendations 2021-10-05 07:13:13 -05:00
Blake Blackshear
0260d824a6 further doc clarifications 2021-10-05 06:57:17 -05:00
Blake Blackshear
0877a7dec7 Create config.yml 2021-10-04 17:20:58 -05:00
Blake Blackshear
4c7919ad69 updated links 2021-10-04 08:54:35 -05:00
Blake Blackshear
4e997124b3 update latest recommendations for reolink 2021-10-04 07:18:53 -05:00
Blake Blackshear
8b040f5c95 optimize images for web 2021-10-04 07:00:30 -05:00
Blake Blackshear
96156805ed Delete bug_report.md 2021-10-03 08:53:19 -05:00
Blake Blackshear
b8d48d7e62 Create support_request.yml 2021-10-03 08:51:53 -05:00
Blake Blackshear
8ca12806ca revert rockchip support for aarch64 2021-10-03 07:43:55 -05:00
Blake Blackshear
de811b7018 delete clean snapshot when duplicate 2021-10-02 06:59:02 -05:00
Blake Blackshear
7bf7365f6c better log message when corrupt segment detected 2021-10-02 06:58:29 -05:00
Blake Blackshear
1daffd92fd docs updates 2021-10-01 07:37:47 -05:00
Blake Blackshear
74986982a0 update docs url 2021-09-26 16:43:26 -05:00
Blake Blackshear
aa807d25ed add affiliate links 2021-09-26 13:37:42 -05:00
Blake Blackshear
cd28869649 fix path 2021-09-26 12:32:41 -05:00
Blake Blackshear
ae97692883 docs config update for netlify 2021-09-26 12:27:01 -05:00
Blake Blackshear
e8e778c6d4 instantiate area field 2021-09-26 09:43:31 -05:00
Kevin Pelzel
5c552a0d71 change theme color from red 2021-09-25 11:11:49 -05:00
Blake Blackshear
0f5dfea9de add support for rockchip hwaccel 2021-09-25 08:25:00 -05:00
Blake Blackshear
e6cdb6a7a2 install docs clarification 2021-09-24 06:45:15 -05:00
Blake Blackshear
1d25936f31 add region/bbox/area to event table 2021-09-23 07:31:48 -05:00
Blake Blackshear
1049673413 run nginx as root
this addresses an issue many have had when using network shares
2021-09-20 19:02:59 -05:00
Blake Blackshear
c3109f808c allow partial days in retention settings 2021-09-20 18:59:16 -05:00
Blake Blackshear
a943ac1308 use s6 to shutdown frigate 2021-09-18 07:40:27 -05:00
Blake Blackshear
96319e795c docs clarification for masks 2021-09-17 19:21:03 -05:00
Blake Blackshear
5a8016de87 simplify logic and fix wrong segments expiring (fixes #1779) 2021-09-17 17:15:16 -05:00
Blake Blackshear
bc350644bd make expiration of deleted camera footage faster 2021-09-17 17:12:03 -05:00
Blake Blackshear
c793500ad2 add udp camera example to docs 2021-09-15 07:33:50 -05:00
Blake Blackshear
1b2134c49e remove clip_ready event type
this doesnt really mean anything more than "end" anymore. new has_clip property added
2021-09-15 07:16:52 -05:00
Blake Blackshear
86a5b46c68 more docs updates 2021-09-14 08:07:36 -05:00
Blake Blackshear
f83d4a58dd add version to the logs on startup 2021-09-13 22:02:23 -05:00
Blake Blackshear
a5f241d5bd cleanup ha notification docs 2021-09-13 22:02:12 -05:00
Blake Blackshear
661f7baa21 fix global live config 2021-09-13 20:33:00 -05:00
50 changed files with 727 additions and 412 deletions

View File

@@ -1,56 +0,0 @@
---
name: Bug report or Support request
about: Bug report or Support request
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what your issue is.
**Version of frigate**
Output from `/api/version`
**Config file**
Include your full config file wrapped in triple back ticks.
```yaml
config here
```
**Frigate container logs**
```
Include relevant log output here
```
**Frigate stats**
```json
Output from frigate's /api/stats endpoint
```
**FFprobe from your camera**
Run the following command and paste output below
```
ffprobe <stream_url>
```
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Computer Hardware**
- OS: [e.g. Ubuntu, Windows]
- Install method: [e.g. Addon, Docker Compose, Docker Command]
- Virtualization: [e.g. Proxmox, Virtualbox]
- Coral Version: [e.g. USB, PCIe, None]
- Network Setup: [e.g. Wired, WiFi]
**Camera Info:**
- Manufacturer: [e.g. Dahua]
- Model: [e.g. IPC-HDW5231R-ZE]
- Resolution: [e.g. 720p]
- FPS: [e.g. 5]
**Additional context**
Add any other context about the problem here.

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1 @@
blank_issues_enabled: false

View File

@@ -0,0 +1,107 @@
name: Support Request
description: Support for Frigate setup or configuration
title: "[Support]: "
labels: ["support", "triage"]
assignees: []
body:
- type: textarea
id: description
attributes:
label: Describe the problem you are having
validations:
required: true
- type: input
id: version
attributes:
label: Version
description: Visible on the Debug page in the Web UI
validations:
required: true
- type: textarea
id: config
attributes:
label: Frigate config file
description: This will be automatically formatted into code, so no need for backticks.
render: yaml
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell
validations:
required: true
- type: textarea
id: ffprobe
attributes:
label: FFprobe output from your camera
description: Run `ffprobe <camera_url>` and provide output below
render: shell
validations:
required: true
- type: textarea
id: stats
attributes:
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:
label: Install method
options:
- HassOS Addon
- Docker Compose
- Docker CLI
validations:
required: true
- type: dropdown
id: coral
attributes:
label: Coral version
options:
- USB
- PCIe
- M.2
- Dev Board
- Other
- CPU (no coral)
validations:
required: true
- type: dropdown
id: network
attributes:
label: Network connection
options:
- Wired
- Wireless
- Mixed
validations:
required: true
- type: input
id: camera
attributes:
label: Camera make and model
description: Dahua, hikvision, amcrest, reolink, etc and model number
validations:
required: true
- type: textarea
id: other
attributes:
label: Any other information that may be helpful

View File

@@ -1,28 +0,0 @@
name: On push
on:
push:
branches:
- master
- release-0.8.0
jobs:
deploy-docs:
name: Deploy docs
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./docs
steps:
- uses: actions/checkout@master
- uses: actions/setup-node@master
with:
node-version: 12.x
- run: npm install
- name: Build docs
run: npm run build
- name: Deploy documentation
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs/build

View File

@@ -39,7 +39,7 @@ aarch64_wheels:
docker build --tag blakeblackshear/frigate-wheels:1.0.3-aarch64 --file docker/Dockerfile.wheels .
aarch64_ffmpeg:
docker build --no-cache --pull --tag blakeblackshear/frigate-ffmpeg:1.2.0-aarch64 --file docker/Dockerfile.ffmpeg.aarch64 .
docker build --no-cache --pull --tag blakeblackshear/frigate-ffmpeg:1.3.0-aarch64 --file docker/Dockerfile.ffmpeg.aarch64 .
aarch64_frigate: version web
docker build --no-cache --tag frigate-base --build-arg ARCH=aarch64 --build-arg FFMPEG_VERSION=1.0.0 --build-arg WHEELS_VERSION=1.0.3 --build-arg NGINX_VERSION=1.0.2 --file docker/Dockerfile.base .

View File

@@ -20,7 +20,7 @@ Use of a [Google Coral Accelerator](https://coral.ai/products/) is optional, but
## Documentation
View the documentation at https://blakeblackshear.github.io/frigate
View the documentation at https://docs.frigate.video
## Donations

View File

@@ -9,7 +9,7 @@ WORKDIR /tmp/workdir
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get -yqq update && \
apt-get install -yq --no-install-recommends ca-certificates expat libgomp1 && \
apt-get install -yq --no-install-recommends ca-certificates expat libgomp1 xutils-dev && \
apt-get autoremove -y && \
apt-get clean -y
@@ -18,7 +18,7 @@ FROM base as build
ENV FFMPEG_VERSION=4.3.2 \
AOM_VERSION=v1.0.0 \
FDKAAC_VERSION=0.1.5 \
FREETYPE_VERSION=2.5.5 \
FREETYPE_VERSION=2.11.0 \
FRIBIDI_VERSION=0.19.7 \
KVAZAAR_VERSION=1.2.0 \
LAME_VERSION=3.100 \
@@ -43,7 +43,7 @@ ENV FFMPEG_VERSION=4.3.2 \
LIBZMQ_VERSION=4.3.2 \
SRC=/usr/local
ARG FREETYPE_SHA256SUM="5d03dd76c2171a7601e9ce10551d52d4471cf92cd205948e60289251daddffa8 freetype-2.5.5.tar.gz"
ARG FREETYPE_SHA256SUM="a45c6b403413abd5706f3582f04c8339d26397c4304b78fa552f2215df64101f freetype-2.11.0.tar.gz"
ARG FRIBIDI_SHA256SUM="3fc96fa9473bd31dcb5500bdf1aa78b337ba13eb8c301e7c28923fea982453a8 0.19.7.tar.gz"
ARG LIBVIDSTAB_SHA256SUM="14d2a053e56edad4f397be0cb3ef8eb1ec3150404ce99a426c4eb641861dc0bb v1.1.0.tar.gz"
ARG OGG_SHA256SUM="e19ee34711d7af328cb26287f4137e70630e7261b17cbe3cd41011d73a654692 libogg-1.3.2.tar.gz"
@@ -392,6 +392,16 @@ RUN \
make -j $(nproc) install && \
rm -rf ${DIR}
RUN \
DIR=/tmp/rkmpp && \
mkdir -p ${DIR} && \
cd ${DIR} && \
git clone https://github.com/rockchip-linux/libdrm-rockchip && git clone https://github.com/rockchip-linux/mpp && \
cd libdrm-rockchip && bash autogen.sh && ./configure && make && make install && \
cd ../mpp && cmake -DRKPLATFORM=ON -DHAVE_DRM=ON && make -j6 && make install && \
rm -rf ${DIR}
## ffmpeg https://ffmpeg.org/
RUN \
DIR=/tmp/ffmpeg && mkdir -p ${DIR} && cd ${DIR} && \
@@ -434,6 +444,8 @@ RUN \
--enable-libkvazaar \
--enable-libaom \
--extra-libs=-lpthread \
--enable-rkmpp \
--enable-libdrm \
# --enable-omx \
# --enable-omx-rpi \
# --enable-mmal \

View File

@@ -1,4 +1,5 @@
daemon off;
user root;
worker_processes 1;
error_log /usr/local/nginx/logs/error.log warn;

View File

@@ -48,3 +48,17 @@ This may need to be in a custom location if network storage is used for the medi
If using a custom model, the width and height will need to be specified.
The labelmap can be customized to your needs. A common reason to do this is to combine multiple object types that are easily confused when you don't need to be as granular such as car/truck. By default, truck is renamed to car because they are often confused. You cannot add new object types, but you can change the names of existing objects in the model.
```yaml
model:
labelmap:
2: vehicle
3: vehicle
5: vehicle
7: vehicle
15: animal
16: animal
17: animal
```
Note that if you rename objects in the labelmap, you will also need to update your `objects -> track` list as well.

View File

@@ -8,19 +8,7 @@ title: Camera Specific Configurations
The input and output parameters need to be adjusted for MJPEG cameras
```yaml
input_args:
- -avoid_negative_ts
- make_zero
- -fflags
- nobuffer
- -flags
- low_delay
- -strict
- experimental
- -fflags
- +genpts+discardcorrupt
- -use_wallclock_as_timestamps
- "1"
input_args: -avoid_negative_ts make_zero -fflags nobuffer -flags low_delay -strict experimental -fflags +genpts+discardcorrupt -use_wallclock_as_timestamps 1
```
Note that mjpeg cameras require encoding the video into h264 for recording, and rtmp roles. This will use significantly more CPU than if the cameras supported h264 feeds directly.
@@ -31,50 +19,69 @@ output_args:
rtmp: -c:v libx264 -an -f flv
```
### RTMP Cameras (Reolink 410/520 and possibly others)
### RTMP Cameras
The input parameters need to be adjusted for RTMP cameras
```yaml
ffmpeg:
input_args:
- -avoid_negative_ts
- make_zero
- -fflags
- nobuffer
- -flags
- low_delay
- -strict
- experimental
- -fflags
- +genpts+discardcorrupt
- -rw_timeout
- "5000000"
- -use_wallclock_as_timestamps
- "1"
- -f
- live_flv
input_args: -avoid_negative_ts make_zero -fflags nobuffer -flags low_delay -strict experimental -fflags +genpts+discardcorrupt -rw_timeout 5000000 -use_wallclock_as_timestamps 1 -f live_flv
```
### Reolink 410/520 (possibly others)
According to [this discussion](https://github.com/blakeblackshear/frigate/issues/1713#issuecomment-932976305), the http video streams seem to be the most reliable for Reolink.
```yaml
cameras:
reolink:
ffmpeg:
hwaccel_args:
input_args:
- -avoid_negative_ts
- make_zero
- -fflags
- nobuffer+genpts+discardcorrupt
- -flags
- low_delay
- -strict
- experimental
- -analyzeduration
- 1000M
- -probesize
- 1000M
- -rw_timeout
- "5000000"
inputs:
- path: http://reolink_ip/flv?port=1935&app=bcs&stream=channel0_main.bcs&user=username&password=password
roles:
- record
- rtmp
- path: http://reolink_ip/flv?port=1935&app=bcs&stream=channel0_ext.bcs&user=username&password=password
roles:
- detect
detect:
width: 640
height: 480
fps: 7
```
![Resolutions](/img/reolink-settings.png)
### Blue Iris RTSP Cameras
You will need to remove `nobuffer` flag for Blue Iris RTSP cameras
```yaml
ffmpeg:
input_args:
- -avoid_negative_ts
- make_zero
- -flags
- low_delay
- -strict
- experimental
- -fflags
- +genpts+discardcorrupt
- -rtsp_transport
- tcp
- -stimeout
- "5000000"
- -use_wallclock_as_timestamps
- "1"
input_args: -avoid_negative_ts make_zero -flags low_delay -strict experimental -fflags +genpts+discardcorrupt -rtsp_transport tcp -stimeout 5000000 -use_wallclock_as_timestamps 1
```
### UDP Only Cameras
If your cameras do not support TCP connections for RTSP, you can use UDP.
```yaml
ffmpeg:
input_args: -avoid_negative_ts make_zero -fflags +genpts+discardcorrupt -rtsp_transport udp -stimeout 5000000 -use_wallclock_as_timestamps 1
```

View File

@@ -36,4 +36,42 @@ motion:
- 0,461,3,0,1919,0,1919,843,1699,492,1344
```
![poly](/img/example-mask-poly.png)
![poly](/img/example-mask-poly-min.png)
### Further Clarification
This is a response to a [question posed on reddit](https://www.reddit.com/r/homeautomation/comments/ppxdve/replacing_my_doorbell_with_a_security_camera_a_6/hd876w4?utm_source=share&utm_medium=web2x&context=3):
It is helpful to understand a bit about how Frigate uses motion detection and object detection together.
First, Frigate uses motion detection as a first line check to see if there is anything happening in the frame worth checking with object detection.
Once motion is detected, it tries to group up nearby areas of motion together in hopes of identifying a rectangle in the image that will capture the area worth inspecting. These are the red "motion boxes" you see in the debug viewer.
After the area with motion is identified, Frigate creates a "region" (the green boxes in the debug viewer) to run object detection on. The models are trained on square images, so these regions are always squares. It adds a margin around the motion area in hopes of capturing a cropped view of the object moving that fills most of the image passed to object detection, but doesn't cut anything off. It also takes into consideration the location of the bounding box from the previous frame if it is tracking an object.
After object detection runs, if there are detected objects that seem to be cut off, Frigate reframes the region and runs object detection again on the same frame to get a better look.
All of this happens for each area of motion and tracked object.
> Are you simply saying that INITIAL triggering of any kind of detection will only happen in un-masked areas, but that once this triggering happens, the masks become irrelevant and object detection takes precedence?
Essentially, yes. I wouldn't describe it as object detection taking precedence though. The motion masks just prevent those areas from being counted as motion. Those masks do not modify the regions passed to object detection in any way, so you can absolutely detect objects in areas masked for motion.
> If so, this is completely expected and intuitive behavior for me. Because obviously if a "foot" starts motion detection the camera should be able to check if it's an entire person before it fully crosses into the zone. The docs imply this is the behavior, so I also don't understand why this would be detrimental to object detection on the whole.
When just a foot is triggering motion, Frigate will zoom in and look only at the foot. If that even qualifies as a person, it will determine the object is being cut off and look again and again until it zooms back out enough to find the whole person.
It is also detrimental to how Frigate tracks a moving object. Motion nearby the bounding box from the previous frame is used to intelligently determine where the region should be in the next frame. With too much masking, tracking is hampered and if an object walks from an unmasked area into a fully masked area, they essentially disappear and will be picked up as a "new" object if they leave the masked area. This is important because Frigate uses the history of scores while tracking an object to determine if it is a false positive or not. It takes a minimum of 3 frames for Frigate to determine is the object type it thinks it is, and the median score must be greater than the threshold. If a person meets this threshold while on the sidewalk before they walk into your stoop, you will get an alert the instant they step a single foot into a zone.
> I thought the main point of this feature was to cut down on CPU use when motion is happening in unnecessary areas.
It is, but the definition of "unnecessary" varies. I want to ignore areas of motion that I know are definitely not being triggered by objects of interest. Timestamps, trees, sky, rooftops. I don't want to ignore motion from objects that I want to track and know where they go.
> For me, giving my masks ANY padding results in a lot of people detection I'm not interested in. I live in the city and catch a lot of the sidewalk on my camera. People walk by my front door all the time and the margin between the sidewalk and actually walking onto my stoop is very thin, so I basically have everything but the exact contours of my stoop masked out. This results in very tidy detections but this info keeps throwing me off. Am I just overthinking it?
This is what `required_zones` are for. You should define a zone (remember this is evaluated based on the bottom center of the bounding box) and make it required to save snapshots and clips (now events in 0.9.0). You can also use this in your conditions for a notification.
> Maybe my specific situation just warrants this. I've just been having a hard time understanding the relevance of this information - it seems to be that it's exactly what would be expected when "masking out" an area of ANY image.
That may be the case for you. Frigate will definitely work harder tracking people on the sidewalk to make sure it doesn't miss anyone who steps foot on your stoop. The trade off with the way you have it now is slower recognition of objects and potential misses. That may be acceptable based on your needs. Also, if your resolution is low enough on the detect stream, your regions may already be so big that they grab the entire object anyway.

View File

@@ -9,6 +9,8 @@ ffmpeg with NVDEC support is required. The special docker architecture 'amd64nvi
includes this support for amd64 platforms. An aarch64 for the Jetson, which
also includes NVDEC may be added in the future.
Some more detailed setup instructions are also available in [this issue](https://github.com/blakeblackshear/frigate/issues/1847#issuecomment-932076731).
## Docker setup
### Requirements

View File

@@ -3,8 +3,23 @@ id: record
title: Recording
---
Recordings can be enabled and are stored at `/media/frigate/recordings`. The folder structure for the recordings is `YYYY-MM/DD/HH/<camera_name>/MM.SS.mp4`. These recordings are written directly from your camera stream without re-encoding. Each camera supports a configurable retention policy in the config.
Exported clips are also created off of these recordings. Frigate chooses the largest matching retention value between the recording retention and the event retention when determining if a recording should be removed.
Recordings can be enabled and are stored at `/media/frigate/recordings`. The folder structure for the recordings is `YYYY-MM/DD/HH/<camera_name>/MM.SS.mp4`. These recordings are written directly from your camera stream without re-encoding. Each camera supports a configurable retention policy in the config. Frigate chooses the largest matching retention value between the recording retention and the event retention when determining if a recording should be removed.
H265 recordings can be viewed in Edge and Safari only. All other browsers require recordings to be encoded with H264.
## What if I don't want 24/7 recordings?
If you only used clips in previous versions with recordings disabled, you can use the following config to get the same behavior. This is also the default behavior when recordings are enabled.
```yaml
record:
enabled: True
retain_days: 0
events:
retain:
default: 10
```
This configuration will retain recording segments that overlap with events for 10 days. Because multiple events can reference the same recording segments, this avoids storing duplicate footage for overlapping events and reduces overall storage needs.
When `retain_days` is set to `0`, events will have up to `max_seconds` (defaults to 5 minutes) of recordings retained. Increasing `retain_days` to `1` will allow events to exceed the `max_seconds` limitation of up to 1 day.

View File

@@ -8,3 +8,31 @@ Zones allow you to define a specific area of the frame and apply additional filt
During testing, enable the Zones option for the debug feed so you can adjust as needed. The zone line will increase in thickness when any object enters the zone.
To create a zone, follow [the steps for a "Motion mask"](/configuration/masks), but use the section of the web UI for creating a zone instead.
### Restricting zones to specific objects
Sometimes you want to limit a zone to specific object types to have more granular control of when events/snapshots are saved. The following example will limit one zone to person objects and the other to cars.
```yaml
camera:
record:
events:
required_zones:
- entire_yard
- front_yard_street
snapshots:
required_zones:
- entire_yard
- front_yard_street
zones:
entire_yard:
coordinates: ... (everywhere you want a person)
objects:
- person
front_yard_street:
coordinates: ... (just the street)
objects:
- car
```
Only car objects can trigger the `front_yard_street` zone and only person can trigger the `entire_yard`. You will get events for person objects that enter anywhere in the yard, and events for cars only if they enter the street.

View File

@@ -13,13 +13,13 @@ A solid green image means that frigate has not received any frames from ffmpeg.
### How can I get sound or audio in my 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](/frigate/configuration/index#ffmpeg).
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#ffmpeg).
### My mjpeg stream or snapshots look green and crazy
This almost always means that the width/height defined for your camera are not correct. Double check the resolution with vlc or another player. Also make sure you don't have the width and height values backwards.
![mismatched-resolution](/img/mismatched-resolution.jpg)
![mismatched-resolution](/img/mismatched-resolution-min.jpg)
### I can't view events or recordings in the Web UI.

View File

@@ -17,7 +17,7 @@ The ideal resolution for detection is one where the objects you want to detect f
Larger resolutions **do** improve performance if the objects are very small in the frame.
![Resolutions](/img/resolutions.png)
![Resolutions](/img/resolutions-min.jpg)
### Example Camera Configuration

View File

@@ -0,0 +1,56 @@
---
id: ha_notifications
title: Home Assistant notifications
---
The best way to get started with notifications for Frigate is to use the [Blueprint](https://community.home-assistant.io/t/frigate-mobile-app-notifications/311091). You can use the yaml generated from the Blueprint as a starting point and customize from there.
It is generally recommended to trigger notifications based on the `frigate/events` mqtt topic. This provides the event_id needed to fetch [thumbnails/snapshots/clips](/integrations/home-assistant#notification-api) and other useful information to customize when and where you want to receive alerts. The data is published in the form of a change feed, which means you can reference the "previous state" of the object in the `before` section and the "current state" of the object in the `after` section. You can see an example [here](/integrations/mqtt#frigateevents).
Here is a simple example of a notification automation of events which will update the existing notification for each change. This means the image you see in the notification will update as frigate finds a "better" image.
```yaml
automation:
- alias: Notify of events
trigger:
platform: mqtt
topic: frigate/events
action:
- service: notify.mobile_app_pixel_3
data_template:
message: 'A {{trigger.payload_json["after"]["label"]}} was detected.'
data:
image: 'https://your.public.hass.address.com/api/frigate/notifications/{{trigger.payload_json["after"]["id"]}}/thumbnail.jpg?format=android'
tag: '{{trigger.payload_json["after"]["id"]}}'
when: '{{trigger.payload_json["after"]["start_time"]|int}}'
```
## Conditions
Conditions with the `before` and `after` values allow a high degree of customization for automations.
When a person enters a zone named yard
```yaml
condition:
- "{{ trigger.payload_json['after']['label'] == 'person' }}"
- "{{ 'yard' in trigger.payload_json['after']['entered_zones'] }}"
```
When a person leaves a zone named yard
```yaml
condition:
- "{{ trigger.payload_json['after']['label'] == 'person' }}"
- "{{ 'yard' in trigger.payload_json['before']['current_zones'] }}"
- "{{ not 'yard' in trigger.payload_json['after']['current_zones'] }}"
```
Notify for dogs in the front with a high top score
```yaml
condition:
- "{{ trigger.payload_json['after']['label'] == 'dog' }}"
- "{{ trigger.payload_json['after']['camera'] == 'front' }}"
- "{{ trigger.payload_json['after']['top_score'] > 0.98 }}"
```

View File

@@ -0,0 +1,37 @@
---
id: stationary_objects
title: Avoiding stationary objects
---
Many people use Frigate to detect cars entering their driveway, and they often run into an issue with repeated events of a parked car being repeatedly detected. This is because object tracking stops when motion ends and the event ends. Motion detection works by determining if a sufficient number of pixels have changed between frames. Shadows or other lighting changes will be detected as motion. This will often cause a new event for a parked car.
You can use zones to restrict events and notifications to objects that have entered specific areas.
:::caution
It is not recommended to use masks to try and eliminate parked cars in your driveway. Masks are designed to prevent motion from triggering object detection and/or to indicate areas that are guaranteed false positives.
Frigate is designed to track objects as they move and over-masking can prevent it from knowing that an object in the current frame is the same as the previous frame. You want Frigate to detect objects everywhere and configure your events and alerts to be based on the location of the object with zones.
:::
To only be notified of cars that enter your driveway from the street, you could create multiple zones that cover your driveway. For cars, you would only notify if `entered_zones` from the events MQTT topic has more than 1 zone.
See [this example](/configuration/zones#restricting-zones-to-specific-objects) from the Zones documentation to see how to restrict zones to certain object types.
![Driveway Zones](/img/driveway_zones-min.png)
To limit snapshots and events, you can list the zone for the entrance of your driveway under `required_zones` in your configuration file. Example below.
```yaml
camera:
record:
events:
required_zones:
- zone_2
zones:
zone_1:
coordinates: ... (parking area)
zone_2:
coordinates: ... (entrance to driveway)
```

View File

@@ -9,25 +9,31 @@ Cameras that output H.264 video and AAC audio will offer the most compatibility
I recommend Dahua, Hikvision, and Amcrest in that order. Dahua edges out Hikvision because they are easier to find and order, not because they are better cameras. I personally use Dahua cameras because they are easier to purchase directly. In my experience Dahua and Hikvision both have multiple streams with configurable resolutions and frame rates and rock solid streams. They also both have models with large sensors well known for excellent image quality at night. Not all the models are equal. Larger sensors are better than higher resolutions; especially at night. Amcrest is the fallback recommendation because they are rebranded Dahuas. They are rebranding the lower end models with smaller sensors or less configuration options.
Many users have reported various issues with Reolink cameras, so I do not recommend them. If you are using Reolink, I suggest the [Reolink specific configuration](configuration/camera_specific#reolink-410520-possibly-others). Wifi cameras are also not recommended. Their streams are less reliable and cause connection loss and/or lost video data.
Here are some of the camera's I recommend:
- [Loryta(Dahua) T5442TM-AS-LED](https://www.amazon.com/Loryta-IPC-T5442TM-AS-LED-Starlight-Eyeball-Network/dp/B07S5QZJDH/)
- [Loryta(Dahua) IPC-T5442TM-AS](https://www.amazon.com/Loryta-IPC-T5442TM-AS-Starlight-Eyeball-Network/dp/B07S21FVC7/)
- [Amcrest IP5M-T1179EW-28MM](https://www.amazon.com/Amcrest-5-Megapixel-NightVision-Weatherproof-IP5M-T1179EW-28MM/dp/B083G9KT4C/)
- <a href="https://amzn.to/3uFLtxB" target="_blank" rel="nofollow noopener sponsored">Loryta(Dahua) T5442TM-AS-LED</a> (affiliate link)
- <a href="https://amzn.to/3isJ3gU" target="_blank" rel="nofollow noopener sponsored">Loryta(Dahua) IPC-T5442TM-AS</a> (affiliate link)
- <a href="https://amzn.to/2ZWNWIA" target="_blank" rel="nofollow noopener sponsored">Amcrest IP5M-T1179EW-28MM</a> (affiliate link)
I may earn a small commission for my endorsement, recommendation, testimonial, or link to any products or services from this website.
## Server
My current favorite is the Minisforum GK41 because the dual NICs allow you to setup a dedicated private network for your cameras where they can be blocked from accessing the internet.
My current favorite is the Odyssey X86 Blue J4125 because the Coral M.2 compatibility and dual NICs that allow you to setup a dedicated private network for your cameras where they can be blocked from accessing the internet. I may earn a small commission for my endorsement, recommendation, testimonial, or link to any products or services from this website.
| Name | Inference Speed | Notes |
| ----------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| Minisforum GK41 | 9-10ms | Great alternative to a NUC with dual Gigabit NICs. Easily handles several 1080p cameras. |
| Intel NUC NUC7i3BNK | 8-10ms | Great performance. Can handle many cameras at 5fps depending on typical amounts of motion. |
| BMAX B2 Plus | 10-12ms | Good balance of performance and cost. Also capable of running many other services at the same time as frigate. |
| Atomic Pi | 16ms | Good option for a dedicated low power board with a small number of cameras. Can leverage Intel QuickSync for stream decoding. |
| Raspberry Pi 3B (32bit) | 60ms | Can handle a small number of cameras, but the detection speeds are slow due to USB 2.0. |
| Raspberry Pi 4 (32bit) | 15-20ms | Can handle a small number of cameras. The 2GB version runs fine. |
| Raspberry Pi 4 (64bit) | 10-15ms | Can handle a small number of cameras. The 2GB version runs fine. |
| Name | Inference Speed | Coral Compatibility | Notes |
| -------------------------------------------------------------------------------------------------------------------------------- | --------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| <a href="https://amzn.to/3oH4BKi" target="_blank" rel="nofollow noopener sponsored">Odyssey X86 Blue J4125</a> (affiliate link) | 9-10ms | M.2 B+M | Dual gigabit NICs for easy isolated camera network. Easily handles several 1080p cameras. |
| <a href="https://amzn.to/3oxEC8m" target="_blank" rel="nofollow noopener sponsored">Minisforum GK41</a> (affiliate link) | 9-10ms | USB | Great alternative to a NUC. Easily handles several 1080p cameras. |
| <a href="https://amzn.to/3ixJFlb" target="_blank" rel="nofollow noopener sponsored">Minisforum GK50</a> (affiliate link) | 9-10ms | USB | Dual gigabit NICs for easy isolated camera network. Easily handles several 1080p cameras. |
| <a href="https://amzn.to/3l7vCEI" target="_blank" rel="nofollow noopener sponsored">Intel NUC</a> (affiliate link) | 8-10ms | USB | Overkill for most, but great performance. Can handle many cameras at 5fps depending on typical amounts of motion. |
| <a href="https://amzn.to/3a6TBh8" target="_blank" rel="nofollow noopener sponsored">BMAX B2 Plus</a> (affiliate link) | 10-12ms | USB | Good balance of performance and cost. Also capable of running many other services at the same time as frigate. |
| <a href="https://amzn.to/2YjpY9m" target="_blank" rel="nofollow noopener sponsored">Atomic Pi</a> (affiliate link) | 16ms | USB | Good option for a dedicated low power board with a small number of cameras. Can leverage Intel QuickSync for stream decoding. |
| <a href="https://amzn.to/2WIpwRU" target="_blank" rel="nofollow noopener sponsored">Raspberry Pi 3B (32bit)</a> (affiliate link) | 60ms | USB | Can handle a small number of cameras, but the detection speeds are slow due to USB 2.0. |
| <a href="https://amzn.to/2YhSGHH" target="_blank" rel="nofollow noopener sponsored">Raspberry Pi 4 (32bit)</a> (affiliate link) | 15-20ms | USB | Can handle a small number of cameras. The 2GB version runs fine. |
| <a href="https://amzn.to/2YhSGHH" target="_blank" rel="nofollow noopener sponsored">Raspberry Pi 4 (64bit)</a> (affiliate link) | 10-15ms | USB | Can handle a small number of cameras. The 2GB version runs fine. |
## Google Coral TPU

View File

@@ -20,6 +20,6 @@ Use of a [Google Coral Accelerator](https://coral.ai/products/) is optional, but
## Screenshots
![Media Browser](/img/media_browser.png)
![Media Browser](/img/media_browser-min.png)
![Notification](/img/notification.png)
![Notification](/img/notification-min.png)

View File

@@ -3,15 +3,17 @@ id: installation
title: Installation
---
Frigate is a Docker container that can be run on any Docker host including as a [HassOS Addon](https://www.home-assistant.io/addons/).
Frigate is a Docker container that can be run on any Docker host including as a [HassOS Addon](https://www.home-assistant.io/addons/). Note that a Home Assistant Addon is **not** the same thing as the integration. The [integration](integrations/home-assistant) is required to integrate Frigate into Home Assistant.
Frigate requires an MQTT broker. If using the Home Assistant integration, Frigate and Home Assistant must be connected to the same MQTT server to function properly.
## Dependencies
**MQTT broker** - Frigate requires an MQTT broker. If using Home Assistant, Frigate and Home Assistant must be connected to the same MQTT broker.
## Preparing your hardware
### Operating System
Frigate runs best with docker installed on bare metal debian-based distributions. For ideal performance, Frigate needs access to underlying hardware for the Coral and GPU devices. Running Frigate in a VM on top of Proxmox, ESXi, Virtualbox, etc. is not recommended. The virtualization layer often introduces a sizable amount of overhead for communication with Coral devices.
Frigate runs best with docker installed on bare metal debian-based distributions. For ideal performance, Frigate needs access to underlying hardware for the Coral and GPU devices. Running Frigate in a VM on top of Proxmox, ESXi, Virtualbox, etc. is not recommended. The virtualization layer often introduces a sizable amount of overhead for communication with Coral devices, but [not in all circumstances](https://github.com/blakeblackshear/frigate/discussions/1837).
Windows is not officially supported, but some users have had success getting it to run under WSL or Virtualbox. Getting the GPU and/or Coral devices properly passed to Frigate may be difficult or impossible. Search previous discussions or issues for help.
@@ -33,7 +35,7 @@ The shm size cannot be set per container for Home Assistant Addons. You must set
By default, the Raspberry Pi limits the amount of memory available to the GPU. In order to use ffmpeg hardware acceleration, you must increase the available memory by setting `gpu_mem` to the maximum recommended value in `config.txt` as described in the [official docs](https://www.raspberrypi.org/documentation/computers/config_txt.html#memory-options).
Additionally, the USB Coral draws a considerable amount of power. If using any other USB devices such as an SSD, you will experience instability due to the Pi not providing enough power to USB devices. You will need to purchase an external USB hub with it's own power supply. Some have reported success with [this](https://www.amazon.com/-/en/RSHTECH-Active-Splitter-Lightweight-Portable/dp/B091F7C5K4).
Additionally, the USB Coral draws a considerable amount of power. If using any other USB devices such as an SSD, you will experience instability due to the Pi not providing enough power to USB devices. You will need to purchase an external USB hub with it's own power supply. Some have reported success with <a href="https://amzn.to/3a2mH0P" target="_blank" rel="nofollow noopener sponsored">this</a> (affiliate link).
## Docker

View File

@@ -1,7 +1,6 @@
---
id: home-assistant
title: Integration with Home Assistant
sidebar_label: Home Assistant
title: Home Assistant Integration
---
The best way to integrate with Home Assistant is to use the [official integration](https://github.com/blakeblackshear/frigate-hass-integration).
@@ -77,131 +76,34 @@ Home Assistant > Configuration > Integrations > Frigate > Options
The integration provides:
- Rich UI with thumbnails for browsing event recordings
- Rich UI for browsing 24/7 recordings by month, day, camera, time
- Browsing event recordings with thumbnails
- Browsing snapshots
- Browsing recordings by month, day, camera, time
This is accessible via "Media Browser" on the left menu panel in Home Assistant.
<a name="api"></a>
## API
## Notification API
- Notification API with public facing endpoints for images in notifications
Many people do not want to expose Frigate to the web, so the integration creates some public API endpoints that can be used for notifications.
### Notifications
Frigate publishes event information in the form of a change feed via MQTT. This
allows lots of customization for notifications to meet your needs. Event changes
are published with `before` and `after` information as shown
[here](#frigateevents). Note that some people may not want to expose frigate to
the web, so you can leverage the HA API that frigate custom_integration ties
into (which is exposed to the web, and thus can be used for mobile notifications
etc):
To load an image taken by frigate from Home Assistants API see below:
To load a thumbnail for an event:
```
https://HA_URL/api/frigate/notifications/<event-id>/thumbnail.jpg
```
To load a video clip taken by frigate from Home Assistants API :
To load a snapshot for an event:
```
https://HA_URL/api/frigate/notifications/<event-id>/<camera>/clip.mp4
https://HA_URL/api/frigate/notifications/<event-id>/snapshot.jpg
```
Here is a simple example of a notification automation of events which will update the existing notification for each change. This means the image you see in the notification will update as frigate finds a "better" image.
To load a video clip of an event:
```yaml
automation:
- alias: Notify of events
trigger:
platform: mqtt
topic: frigate/events
action:
- service: notify.mobile_app_pixel_3
data_template:
message: 'A {{trigger.payload_json["after"]["label"]}} was detected.'
data:
image: 'https://your.public.hass.address.com/api/frigate/notifications/{{trigger.payload_json["after"]["id"]}}/thumbnail.jpg?format=android'
tag: '{{trigger.payload_json["after"]["id"]}}'
when: '{{trigger.payload_json["after"]["start_time"]|int}}'
```
```yaml
automation:
- alias: When a person enters a zone named yard
trigger:
platform: mqtt
topic: frigate/events
condition:
- "{{ trigger.payload_json['after']['label'] == 'person' }}"
- "{{ 'yard' in trigger.payload_json['after']['entered_zones'] }}"
action:
- service: notify.mobile_app_pixel_3
data_template:
message: "A {{trigger.payload_json['after']['label']}} has entered the yard."
data:
image: "https://url.com/api/frigate/notifications/{{trigger.payload_json['after']['id']}}/thumbnail.jpg"
tag: "{{trigger.payload_json['after']['id']}}"
when: '{{trigger.payload_json["after"]["start_time"]|int}}'
```
```yaml
- alias: When a person leaves a zone named yard
trigger:
platform: mqtt
topic: frigate/events
condition:
- "{{ trigger.payload_json['after']['label'] == 'person' }}"
- "{{ 'yard' in trigger.payload_json['before']['current_zones'] }}"
- "{{ not 'yard' in trigger.payload_json['after']['current_zones'] }}"
action:
- service: notify.mobile_app_pixel_3
data_template:
message: "A {{trigger.payload_json['after']['label']}} has left the yard."
data:
image: "https://url.com/api/frigate/notifications/{{trigger.payload_json['after']['id']}}/thumbnail.jpg"
tag: "{{trigger.payload_json['after']['id']}}"
when: '{{trigger.payload_json["after"]["start_time"]|int}}'
```
```yaml
- alias: Notify for dogs in the front with a high top score
trigger:
platform: mqtt
topic: frigate/events
condition:
- "{{ trigger.payload_json['after']['label'] == 'dog' }}"
- "{{ trigger.payload_json['after']['camera'] == 'front' }}"
- "{{ trigger.payload_json['after']['top_score'] > 0.98 }}"
action:
- service: notify.mobile_app_pixel_3
data_template:
message: "High confidence dog detection."
data:
image: "https://url.com/api/frigate/notifications/{{trigger.payload_json['after']['id']}}/thumbnail.jpg"
tag: "{{trigger.payload_json['after']['id']}}"
when: '{{trigger.payload_json["after"]["start_time"]|int}}'
```
If you are using telegram, you can fetch the image directly from Frigate:
```yaml
automation:
- alias: Notify of events
trigger:
platform: mqtt
topic: frigate/events
action:
- service: notify.telegram_full
data_template:
message: 'A {{trigger.payload_json["after"]["label"]}} was detected.'
data:
photo:
# this url should work for addon users
- url: 'http://ccab4aaf-frigate:5000/api/events/{{trigger.payload_json["after"]["id"]}}/thumbnail.jpg'
caption: 'A {{trigger.payload_json["after"]["label"]}} was detected on {{ trigger.payload_json["after"]["camera"] }} camera'
https://HA_URL/api/frigate/notifications/<event-id>/clip.mp4
```
<a name="streams"></a>
@@ -282,6 +184,6 @@ which server they are referring to.
## FAQ
### If I am detecting multiple objects, how do I assign the correct `binary_sensor` to the camera in HomeKit?
#### If I am detecting multiple objects, how do I assign the correct `binary_sensor` to the camera in HomeKit?
The [HomeKit integration](https://www.home-assistant.io/integrations/homekit/) randomly links one of the binary sensors (motion sensor entities) grouped with the camera device in Home Assistant. You can specify a `linked_motion_sensor` in the Home Assistant [HomeKit configuration](https://www.home-assistant.io/integrations/homekit/#linked_motion_sensor) for each camera.

View File

@@ -1,11 +0,0 @@
---
id: howtos
title: Community Guides
sidebar_label: Community Guides
---
## Communitiy Guides/How-To's
- Best Camera AI Person & Object Detection - How to Setup Frigate w/ Home Assistant - digiblurDIY [YouTube](https://youtu.be/V8vGdoYO6-Y) - [Article](https://www.digiblur.com/2021/05/how-to-setup-frigate-home-assistant.html)
- Even More Free Local Object Detection with Home Assistant - Frigate Install - Everything Smart Home [YouTube](https://youtu.be/pqDCEZSVeRk)
- Home Assistant Frigate integration for local image recognition - KPeyanski [YouTube](https://youtu.be/Q2UT78lFQpo) - [Article](https://peyanski.com/home-assistant-frigate-integration/)

View File

@@ -36,7 +36,7 @@ Message published for each changed event. The first message is published when th
```json
{
"type": "update", // new, update, end or clip_ready
"type": "update", // new, update, end
"before": {
"id": "1607123955.475377-mxklsc",
"camera": "front_door",
@@ -53,7 +53,9 @@ Message published for each changed event. The first message is published when th
"region": [264, 450, 667, 853],
"current_zones": ["driveway"],
"entered_zones": ["yard", "driveway"],
"thumbnail": null
"thumbnail": null,
"has_snapshot": false,
"has_clip": false
},
"after": {
"id": "1607123955.475377-mxklsc",
@@ -71,7 +73,9 @@ Message published for each changed event. The first message is published when th
"region": [218, 440, 693, 915],
"current_zones": ["yard", "driveway"],
"entered_zones": ["yard", "driveway"],
"thumbnail": null
"thumbnail": null,
"has_snapshot": false,
"has_clip": false
}
}
```

View File

@@ -3,8 +3,8 @@ const path = require('path');
module.exports = {
title: 'Frigate',
tagline: 'NVR With Realtime Object Detection for IP Cameras',
url: 'https://blakeblackshear.github.io',
baseUrl: '/frigate/',
url: 'https://docs.frigate.video',
baseUrl: '/',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
favicon: 'img/favicon.ico',

View File

@@ -9,6 +9,8 @@ module.exports = {
'guides/camera_setup',
'guides/getting_started',
'guides/false_positives',
'guides/ha_notifications',
'guides/stationary_objects',
],
Configuration: [
'configuration/index',

BIN
docs/static/img/driveway_zones-min.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

BIN
docs/static/img/driveway_zones.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

BIN
docs/static/img/media_browser-min.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/static/img/notification-min.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

BIN
docs/static/img/reolink-settings.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/static/img/resolutions-min.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -20,14 +20,14 @@ from frigate.events import EventCleanup, EventProcessor
from frigate.http import create_app
from frigate.log import log_process, root_configurer
from frigate.models import Event, Recordings
from frigate.mqtt import create_mqtt_client, MqttSocketRelay
from frigate.mqtt import MqttSocketRelay, create_mqtt_client
from frigate.object_processing import TrackedObjectProcessor
from frigate.output import output_frames
from frigate.record import RecordingCleanup, RecordingMaintainer
from frigate.stats import StatsEmitter, stats_init
from frigate.version import VERSION
from frigate.video import capture_camera, track_camera
from frigate.watchdog import FrigateWatchdog
from frigate.zeroconf import broadcast_zeroconf
logger = logging.getLogger(__name__)
@@ -315,6 +315,7 @@ class FrigateApp:
def start(self):
self.init_logger()
logger.info(f"Starting Frigate ({VERSION})")
try:
try:
self.init_config()

View File

@@ -66,8 +66,8 @@ class MqttConfig(FrigateBaseModel):
class RetainConfig(FrigateBaseModel):
default: int = Field(default=10, title="Default retention period.")
objects: Dict[str, int] = Field(
default: float = Field(default=10, title="Default retention period.")
objects: Dict[str, float] = Field(
default_factory=dict, title="Object retention period."
)
@@ -90,7 +90,7 @@ class EventsConfig(FrigateBaseModel):
class RecordConfig(FrigateBaseModel):
enabled: bool = Field(default=False, title="Enable record on all cameras.")
retain_days: int = Field(default=0, title="Recording retention period in days.")
retain_days: float = Field(default=0, title="Recording retention period in days.")
events: EventsConfig = Field(
default_factory=EventsConfig, title="Event specific settings."
)
@@ -677,6 +677,9 @@ class FrigateConfig(FrigateBaseModel):
snapshots: SnapshotsConfig = Field(
default_factory=SnapshotsConfig, title="Global snapshots configuration."
)
live: CameraLiveConfig = Field(
default_factory=CameraLiveConfig, title="Global live configuration."
)
rtmp: RtmpConfig = Field(
default_factory=RtmpConfig, title="Global RTMP restreaming configuration."
)
@@ -715,6 +718,7 @@ class FrigateConfig(FrigateBaseModel):
include={
"record": ...,
"snapshots": ...,
"live": ...,
"rtmp": ...,
"objects": ...,
"motion": ...,

View File

@@ -29,38 +29,6 @@ class EventProcessor(threading.Thread):
self.events_in_process = {}
self.stop_event = stop_event
def should_create_clip(self, camera, event_data):
if event_data["false_positive"]:
return False
record_config: RecordConfig = self.config.cameras[camera].record
# Recording is disabled
if not record_config.enabled:
return False
# If there are required zones and there is no overlap
required_zones = record_config.events.required_zones
if len(required_zones) > 0 and not set(event_data["entered_zones"]) & set(
required_zones
):
logger.debug(
f"Not creating clip for {event_data['id']} because it did not enter required zones"
)
return False
# If the required objects are not present
if (
record_config.events.objects is not None
and event_data["label"] not in record_config.events.objects
):
logger.debug(
f"Not creating clip for {event_data['id']} because it did not contain required objects"
)
return False
return True
def run(self):
while not self.stop_event.is_set():
try:
@@ -74,11 +42,9 @@ class EventProcessor(threading.Thread):
self.events_in_process[event_data["id"]] = event_data
if event_type == "end":
has_clip = self.should_create_clip(camera, event_data)
event_config: EventsConfig = self.config.cameras[camera].record.events
if has_clip or event_data["has_snapshot"]:
if event_data["has_clip"] or event_data["has_snapshot"]:
Event.create(
id=event_data["id"],
label=event_data["label"],
@@ -89,12 +55,15 @@ class EventProcessor(threading.Thread):
false_positive=event_data["false_positive"],
zones=list(event_data["entered_zones"]),
thumbnail=event_data["thumbnail"],
has_clip=has_clip,
region=event_data["region"],
box=event_data["box"],
area=event_data["area"],
has_clip=event_data["has_clip"],
has_snapshot=event_data["has_snapshot"],
)
del self.events_in_process[event_data["id"]]
self.event_processed_queue.put((event_data["id"], camera, has_clip))
self.event_processed_queue.put((event_data["id"], camera))
logger.info(f"Exiting event processor...")
@@ -223,12 +192,12 @@ class EventCleanup(threading.Thread):
for event in duplicate_events:
logger.debug(f"Removing duplicate: {event.id}")
media_name = f"{event.camera}-{event.id}"
if event.has_snapshot:
media_path = Path(f"{os.path.join(CLIPS_DIR, media_name)}.jpg")
media_path.unlink(missing_ok=True)
if event.has_clip:
media_path = Path(f"{os.path.join(CLIPS_DIR, media_name)}.mp4")
media_path.unlink(missing_ok=True)
media_path = Path(f"{os.path.join(CLIPS_DIR, media_name)}.jpg")
media_path.unlink(missing_ok=True)
media_path = Path(f"{os.path.join(CLIPS_DIR, media_name)}-clean.png")
media_path.unlink(missing_ok=True)
media_path = Path(f"{os.path.join(CLIPS_DIR, media_name)}.mp4")
media_path.unlink(missing_ok=True)
(
Event.delete()

View File

@@ -15,6 +15,9 @@ class Event(Model):
thumbnail = TextField()
has_clip = BooleanField(default=True)
has_snapshot = BooleanField(default=True)
region = JSONField()
box = JSONField()
area = IntegerField()
class Recordings(Model):

View File

@@ -16,7 +16,7 @@ from typing import Callable, Dict
import cv2
import numpy as np
from frigate.config import CameraConfig, FrigateConfig
from frigate.config import CameraConfig, SnapshotsConfig, RecordConfig, FrigateConfig
from frigate.const import CACHE_DIR, CLIPS_DIR, RECORD_DIR
from frigate.edgetpu import load_labels
from frigate.util import (
@@ -73,6 +73,8 @@ class TrackedObject:
self.current_zones = []
self.entered_zones = set()
self.false_positive = True
self.has_clip = False
self.has_snapshot = False
self.top_score = self.computed_score = 0.0
self.thumbnail_data = None
self.last_updated = 0
@@ -176,6 +178,8 @@ class TrackedObject:
"region": self.obj_data["region"],
"current_zones": self.current_zones.copy(),
"entered_zones": list(self.entered_zones).copy(),
"has_clip": self.has_clip,
"has_snapshot": self.has_snapshot,
}
if include_thumbnail:
@@ -611,9 +615,46 @@ class TrackedObjectProcessor(threading.Thread):
obj.previous = after
def end(camera, obj: TrackedObject, current_frame_time):
snapshot_config = self.config.cameras[camera].snapshots
event_data = obj.to_dict(include_thumbnail=True)
event_data["has_snapshot"] = False
# populate has_snapshot
obj.has_snapshot = self.should_save_snapshot(camera, obj)
obj.has_clip = self.should_retain_recording(camera, obj)
# write the snapshot to disk
if obj.has_snapshot:
snapshot_config: SnapshotsConfig = self.config.cameras[camera].snapshots
jpg_bytes = obj.get_jpg_bytes(
timestamp=snapshot_config.timestamp,
bounding_box=snapshot_config.bounding_box,
crop=snapshot_config.crop,
height=snapshot_config.height,
quality=snapshot_config.quality,
)
if jpg_bytes is None:
logger.warning(f"Unable to save snapshot for {obj.obj_data['id']}.")
else:
with open(
os.path.join(CLIPS_DIR, f"{camera}-{obj.obj_data['id']}.jpg"),
"wb",
) as j:
j.write(jpg_bytes)
# write clean snapshot if enabled
if snapshot_config.clean_copy:
png_bytes = obj.get_clean_png()
if png_bytes is None:
logger.warning(
f"Unable to save clean snapshot for {obj.obj_data['id']}."
)
else:
with open(
os.path.join(
CLIPS_DIR,
f"{camera}-{obj.obj_data['id']}-clean.png",
),
"wb",
) as p:
p.write(png_bytes)
if not obj.false_positive:
message = {
"before": obj.previous,
@@ -623,46 +664,8 @@ class TrackedObjectProcessor(threading.Thread):
self.client.publish(
f"{self.topic_prefix}/events", json.dumps(message), retain=False
)
# write snapshot to disk if enabled
if snapshot_config.enabled and self.should_save_snapshot(camera, obj):
jpg_bytes = obj.get_jpg_bytes(
timestamp=snapshot_config.timestamp,
bounding_box=snapshot_config.bounding_box,
crop=snapshot_config.crop,
height=snapshot_config.height,
quality=snapshot_config.quality,
)
if jpg_bytes is None:
logger.warning(
f"Unable to save snapshot for {obj.obj_data['id']}."
)
else:
with open(
os.path.join(
CLIPS_DIR, f"{camera}-{obj.obj_data['id']}.jpg"
),
"wb",
) as j:
j.write(jpg_bytes)
event_data["has_snapshot"] = True
# write clean snapshot if enabled
if snapshot_config.clean_copy:
png_bytes = obj.get_clean_png()
if png_bytes is None:
logger.warning(
f"Unable to save clean snapshot for {obj.obj_data['id']}."
)
else:
with open(
os.path.join(
CLIPS_DIR,
f"{camera}-{obj.obj_data['id']}-clean.png",
),
"wb",
) as p:
p.write(png_bytes)
self.event_queue.put(("end", camera, event_data))
self.event_queue.put(("end", camera, obj.to_dict(include_thumbnail=True)))
def snapshot(camera, obj: TrackedObject, current_frame_time):
mqtt_config = self.config.cameras[camera].mqtt
@@ -711,8 +714,16 @@ class TrackedObjectProcessor(threading.Thread):
self.zone_data = defaultdict(lambda: defaultdict(dict))
def should_save_snapshot(self, camera, obj: TrackedObject):
if obj.false_positive:
return False
snapshot_config: SnapshotsConfig = self.config.cameras[camera].snapshots
if not snapshot_config.enabled:
return False
# if there are required zones and there is no overlap
required_zones = self.config.cameras[camera].snapshots.required_zones
required_zones = snapshot_config.required_zones
if len(required_zones) > 0 and not obj.entered_zones & set(required_zones):
logger.debug(
f"Not creating snapshot for {obj.obj_data['id']} because it did not enter required zones"
@@ -721,6 +732,36 @@ class TrackedObjectProcessor(threading.Thread):
return True
def should_retain_recording(self, camera, obj: TrackedObject):
if obj.false_positive:
return False
record_config: RecordConfig = self.config.cameras[camera].record
# Recording is disabled
if not record_config.enabled:
return False
# If there are required zones and there is no overlap
required_zones = record_config.events.required_zones
if len(required_zones) > 0 and not set(obj.entered_zones) & set(required_zones):
logger.debug(
f"Not creating clip for {obj.obj_data['id']} because it did not enter required zones"
)
return False
# If the required objects are not present
if (
record_config.events.objects is not None
and obj.obj_data["label"] not in record_config.events.objects
):
logger.debug(
f"Not creating clip for {obj.obj_data['id']} because it did not contain required objects"
)
return False
return True
def should_mqtt_snapshot(self, camera, obj: TrackedObject):
# if there are required zones and there is no overlap
required_zones = self.config.cameras[camera].mqtt.required_zones
@@ -815,17 +856,7 @@ class TrackedObjectProcessor(threading.Thread):
# cleanup event finished queue
while not self.event_processed_queue.empty():
event_id, camera, clip_created = self.event_processed_queue.get()
if clip_created:
obj = self.camera_states[camera].tracked_objects[event_id]
message = {
"before": obj.previous,
"after": obj.to_dict(),
"type": "clip_ready",
}
self.client.publish(
f"{self.topic_prefix}/events", json.dumps(message), retain=False
)
event_id, camera = self.event_processed_queue.get()
self.camera_states[camera].finished(event_id)
logger.info(f"Exiting object processor...")

View File

@@ -99,7 +99,7 @@ class RecordingMaintainer(threading.Thread):
duration = float(p.stdout.decode().strip())
end_time = start_time + datetime.timedelta(seconds=duration)
else:
logger.info(f"bad file: {f}")
logger.warning(f"Discarding a corrupt recording segment: {f}")
Path(cache_path).unlink(missing_ok=True)
continue
@@ -166,9 +166,13 @@ class RecordingCleanup(threading.Thread):
Recordings.end_time < expire_before,
)
deleted_recordings = set()
for recording in no_camera_recordings:
Path(recording.path).unlink(missing_ok=True)
Recordings.delete_by_id(recording.id)
deleted_recordings.add(recording.id)
logger.debug(f"Expiring {len(deleted_recordings)} recordings")
Recordings.delete().where(Recordings.id << deleted_recordings).execute()
logger.debug("End deleted cameras.")
logger.debug("Start all cameras.")
@@ -192,7 +196,7 @@ class RecordingCleanup(threading.Thread):
Recordings.camera == camera,
Recordings.end_time < expire_date,
)
.order_by(Recordings.start_time.desc())
.order_by(Recordings.start_time)
)
# Get all the events to check against
@@ -201,7 +205,7 @@ class RecordingCleanup(threading.Thread):
.where(
Event.camera == camera, Event.end_time < expire_date, Event.has_clip
)
.order_by(Event.start_time.desc())
.order_by(Event.start_time)
.objects()
)
@@ -210,21 +214,28 @@ class RecordingCleanup(threading.Thread):
deleted_recordings = set()
for recording in recordings.objects().iterator():
keep = False
# since the events and recordings are sorted, we can skip events
# that start after the previous recording segment ended
# Now look for a reason to keep this recording segment
for idx in range(event_start, len(events)):
event = events[idx]
# if the next event ends before this segment starts, break
if event.end_time < recording.start_time:
# if the event starts in the future, stop checking events
# and let this recording segment expire
if event.start_time > recording.end_time:
keep = False
break
# if the next event starts after the current segment ends, skip it
if event.start_time > recording.end_time:
event_start = idx
continue
# if the event ends after the recording starts, keep it
# and stop looking at events
if event.end_time >= recording.start_time:
keep = True
break
keep = True
# if the event ends before this recording segment starts, skip
# this event and check the next event for an overlap.
# since the events and recordings are sorted, we can skip events
# that end before the previous recording segment started on future segments
if event.end_time < recording.start_time:
event_start = idx
# Delete recordings outside of the retention window
if not keep:
@@ -232,7 +243,7 @@ class RecordingCleanup(threading.Thread):
deleted_recordings.add(recording.id)
logger.debug(f"Expiring {len(deleted_recordings)} recordings")
(Recordings.delete().where(Recordings.id << deleted_recordings).execute())
Recordings.delete().where(Recordings.id << deleted_recordings).execute()
logger.debug(f"End camera: {camera}.")

View File

@@ -958,6 +958,81 @@ class TestConfig(unittest.TestCase):
runtime_config = frigate_config.runtime_config
assert runtime_config.cameras["back"].rtmp.enabled
def test_global_live(self):
config = {
"mqtt": {"host": "mqtt"},
"live": {"quality": 4},
"cameras": {
"back": {
"ffmpeg": {
"inputs": [
{
"path": "rtsp://10.0.0.1:554/video",
"roles": ["detect"],
},
]
},
}
},
}
frigate_config = FrigateConfig(**config)
assert config == frigate_config.dict(exclude_unset=True)
runtime_config = frigate_config.runtime_config
assert runtime_config.cameras["back"].live.quality == 4
def test_default_live(self):
config = {
"mqtt": {"host": "mqtt"},
"cameras": {
"back": {
"ffmpeg": {
"inputs": [
{
"path": "rtsp://10.0.0.1:554/video",
"roles": ["detect"],
},
]
}
}
},
}
frigate_config = FrigateConfig(**config)
assert config == frigate_config.dict(exclude_unset=True)
runtime_config = frigate_config.runtime_config
assert runtime_config.cameras["back"].live.quality == 8
def test_global_live_merge(self):
config = {
"mqtt": {"host": "mqtt"},
"live": {"quality": 4, "height": 480},
"cameras": {
"back": {
"ffmpeg": {
"inputs": [
{
"path": "rtsp://10.0.0.1:554/video",
"roles": ["detect"],
},
]
},
"live": {
"quality": 7,
},
}
},
}
frigate_config = FrigateConfig(**config)
assert config == frigate_config.dict(exclude_unset=True)
runtime_config = frigate_config.runtime_config
assert runtime_config.cameras["back"].live.quality == 7
assert runtime_config.cameras["back"].live.height == 480
def test_global_timestamp_style(self):
config = {
@@ -1032,6 +1107,30 @@ class TestConfig(unittest.TestCase):
assert runtime_config.cameras["back"].timestamp_style.position == "bl"
assert runtime_config.cameras["back"].timestamp_style.thickness == 4
def test_allow_retain_to_be_a_decimal(self):
config = {
"mqtt": {"host": "mqtt"},
"snapshots": {"retain": {"default": 1.5}},
"cameras": {
"back": {
"ffmpeg": {
"inputs": [
{
"path": "rtsp://10.0.0.1:554/video",
"roles": ["detect"],
},
]
},
}
},
}
frigate_config = FrigateConfig(**config)
assert config == frigate_config.dict(exclude_unset=True)
runtime_config = frigate_config.runtime_config
assert runtime_config.cameras["back"].snapshots.retain.default == 1.5
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -18,6 +18,7 @@ import cv2
import matplotlib.pyplot as plt
import numpy as np
import os
import psutil
logger = logging.getLogger(__name__)
@@ -534,7 +535,13 @@ def clipped(obj, frame_shape):
def restart_frigate():
os.kill(os.getpid(), signal.SIGTERM)
proc = psutil.Process(1)
# if this is running via s6, sigterm pid 1
if proc.name() == "s6-svscan":
proc.terminate()
# otherwise, just try and exit frigate
else:
os.kill(os.getpid(), signal.SIGTERM)
class EventsPerSecond:

View File

@@ -5,6 +5,10 @@ import time
import os
import signal
from frigate.util import (
restart_frigate,
)
logger = logging.getLogger(__name__)
@@ -30,6 +34,6 @@ class FrigateWatchdog(threading.Thread):
detector.start_or_restart()
elif not detector.detect_process.is_alive():
logger.info("Detection appears to have stopped. Exiting frigate...")
os.kill(os.getpid(), signal.SIGTERM)
restart_frigate()
logger.info(f"Exiting watchdog...")

View File

@@ -0,0 +1,48 @@
"""Peewee migrations -- 004_add_bbox_region_area.py.
Some examples (model - class or model name)::
> Model = migrator.orm['model_name'] # Return model in current state by name
> migrator.sql(sql) # Run custom SQL
> migrator.python(func, *args, **kwargs) # Run python code
> migrator.create_model(Model) # Create a model (could be used as decorator)
> migrator.remove_model(model, cascade=True) # Remove a model
> migrator.add_fields(model, **fields) # Add fields to a model
> migrator.change_fields(model, **fields) # Change fields
> migrator.remove_fields(model, *field_names, cascade=True)
> migrator.rename_field(model, old_field_name, new_field_name)
> migrator.rename_table(model, new_table_name)
> migrator.add_index(model, *col_names, unique=False)
> migrator.drop_index(model, *col_names)
> migrator.add_not_null(model, *field_names)
> migrator.drop_not_null(model, *field_names)
> migrator.add_default(model, field_name, default)
"""
import datetime as dt
import peewee as pw
from playhouse.sqlite_ext import *
from decimal import ROUND_HALF_EVEN
from frigate.models import Event
try:
import playhouse.postgres_ext as pw_pext
except ImportError:
pass
SQL = pw.SQL
def migrate(migrator, database, fake=False, **kwargs):
migrator.add_fields(
Event,
region=JSONField(default=[]),
box=JSONField(default=[]),
area=pw.IntegerField(default=0),
)
def rollback(migrator, database, fake=False, **kwargs):
migrator.remove_fields(Event, ["region", "box", "area"])

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -11,7 +11,8 @@
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#3b82f7" />
<meta name="msapplication-TileColor" content="#3b82f7" />
<meta name="theme-color" content="#ff0000" />
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#111827" media="(prefers-color-scheme: dark)" />
</head>
<body>
<div id="root" class="z-0"></div>

View File

@@ -13,7 +13,7 @@
"type": "image/png"
}
],
"theme_color": "#ff0000",
"background_color": "#ff0000",
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

View File

@@ -61,7 +61,7 @@ export default function Sidebar() {
<Separator />
</Fragment>
) : null}
<Destination className="self-end" href="https://blakeblackshear.github.io/frigate" text="Documentation" />
<Destination className="self-end" href="https://docs.frigate.video" text="Documentation" />
<Destination className="self-end" href="https://github.com/blakeblackshear/frigate" text="GitHub" />
</NavigationDrawer>
);

View File

@@ -57,7 +57,6 @@ describe('Event Route', () => {
const mockEvent = {
camera: 'front',
end_time: 1613257337.841237,
false_positive: false,
has_clip: true,
has_snapshot: true,
id: '1613257326.237365-83cgl2',

View File

@@ -71,7 +71,6 @@ describe('Events Route', () => {
const mockEvents = new Array(12).fill(null).map((v, i) => ({
end_time: 1613257337 + i,
false_positive: false,
has_clip: true,
has_snapshot: true,
id: i,