mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-06 11:15:21 +03:00
Merge branch 'dev' into zooming
This commit is contained in:
commit
2b52028d15
@ -71,22 +71,22 @@ ENV CCACHE_MAXSIZE 2G
|
||||
|
||||
# Build libUSB without udev. Needed for Openvino NCS2 support
|
||||
WORKDIR /opt
|
||||
RUN apt-get update && apt-get install -y unzip build-essential automake libtool ccache
|
||||
RUN --mount=type=cache,target=/root/.ccache wget -q https://github.com/libusb/libusb/archive/v1.0.25.zip -O v1.0.25.zip && \
|
||||
unzip v1.0.25.zip && cd libusb-1.0.25 && \
|
||||
RUN apt-get update && apt-get install -y unzip build-essential automake libtool ccache pkg-config
|
||||
RUN --mount=type=cache,target=/root/.ccache wget -q https://github.com/libusb/libusb/archive/v1.0.26.zip -O v1.0.26.zip && \
|
||||
unzip v1.0.26.zip && cd libusb-1.0.26 && \
|
||||
./bootstrap.sh && \
|
||||
./configure CC='ccache gcc' CCX='ccache g++' --disable-udev --enable-shared && \
|
||||
make -j $(nproc --all)
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends libusb-1.0-0-dev && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /opt/libusb-1.0.25/libusb
|
||||
WORKDIR /opt/libusb-1.0.26/libusb
|
||||
RUN /bin/mkdir -p '/usr/local/lib' && \
|
||||
/bin/bash ../libtool --mode=install /usr/bin/install -c libusb-1.0.la '/usr/local/lib' && \
|
||||
/bin/mkdir -p '/usr/local/include/libusb-1.0' && \
|
||||
/usr/bin/install -c -m 644 libusb.h '/usr/local/include/libusb-1.0' && \
|
||||
/bin/mkdir -p '/usr/local/lib/pkgconfig' && \
|
||||
cd /opt/libusb-1.0.25/ && \
|
||||
cd /opt/libusb-1.0.26/ && \
|
||||
/usr/bin/install -c -m 644 libusb-1.0.pc '/usr/local/lib/pkgconfig' && \
|
||||
ldconfig
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
NGINX_VERSION="1.25.1"
|
||||
NGINX_VERSION="1.25.2"
|
||||
VOD_MODULE_VERSION="1.31"
|
||||
SECURE_TOKEN_MODULE_VERSION="1.5"
|
||||
RTMP_MODULE_VERSION="1.2.2"
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
s6_version="3.1.4.1"
|
||||
s6_version="3.1.5.0"
|
||||
|
||||
if [[ "${TARGETARCH}" == "amd64" ]]; then
|
||||
s6_arch="x86_64"
|
||||
|
||||
@ -39,6 +39,10 @@ if go2rtc_config.get("api") is None:
|
||||
elif go2rtc_config["api"].get("origin") is None:
|
||||
go2rtc_config["api"]["origin"] = "*"
|
||||
|
||||
# Need to set default location for HA config
|
||||
if go2rtc_config.get("hass") is None:
|
||||
go2rtc_config["hass"] = {"config": "/config"}
|
||||
|
||||
# we want to ensure that logs are easy to read
|
||||
if go2rtc_config.get("log") is None:
|
||||
go2rtc_config["log"] = {"format": "text"}
|
||||
|
||||
@ -50,7 +50,7 @@ cameras:
|
||||
|
||||
### Configuring Audio Events
|
||||
|
||||
The included audio model has over 500 different types of audio that can be detected, many of which are not practical. By default `bark`, `speech`, `yell`, and `scream` are enabled but these can be customized.
|
||||
The included audio model has over [500 different types](https://github.com/blakeblackshear/frigate/blob/dev/audio-labelmap.txt) of audio that can be detected, many of which are not practical. By default `bark`, `speech`, `yell`, and `scream` are enabled but these can be customized.
|
||||
|
||||
```yaml
|
||||
audio:
|
||||
|
||||
@ -11,7 +11,7 @@ Once Frigate determines that an object is not a false positive and has entered o
|
||||
|
||||
Upon loss of tracking, Frigate will scan the region of the lost object for `timeout` seconds. If an object of the same type is found in that region, Frigate will autotrack that new object.
|
||||
|
||||
When tracking has ended, Frigate will return to the camera preset specified by the `return_preset` configuration entry.
|
||||
When tracking has ended, Frigate will return to the camera firmware's PTZ preset specified by the `return_preset` configuration entry.
|
||||
|
||||
## Checking ONVIF camera support
|
||||
|
||||
@ -23,7 +23,7 @@ Alternatively, you can download and run [this simple Python script](https://gist
|
||||
|
||||
## Configuration
|
||||
|
||||
First, set up a PTZ preset in your camera's firmware and give it a name.
|
||||
First, set up a PTZ preset in your camera's firmware and give it a name. If you're unsure how to do this, consult the documentation for your camera manufacturer's firmware. Some tutorials for common brands: [Amcrest](https://www.youtube.com/watch?v=lJlE9-krmrM), [Reolink](https://www.youtube.com/watch?v=VAnxHUY5i5w), [Dahua](https://www.youtube.com/watch?v=7sNbc5U-k54).
|
||||
|
||||
Edit your Frigate configuration file and enter the ONVIF parameters for your camera. Specify the object types to track, a required zone the object must enter to begin autotracking, and the camera preset name you configured in your camera's firmware to return to when tracking has ended. Optionally, specify a delay in seconds before Frigate returns the camera to the preset.
|
||||
|
||||
@ -56,7 +56,7 @@ cameras:
|
||||
# Required: Begin automatically tracking an object when it enters any of the listed zones.
|
||||
required_zones:
|
||||
- zone_name
|
||||
# Required: Name of ONVIF camera preset to return to when tracking is over. (default: shown below)
|
||||
# Required: Name of ONVIF preset in camera's firmware to return to when tracking is over. (default: shown below)
|
||||
return_preset: home
|
||||
# Optional: Seconds to delay before returning to preset. (default: shown below)
|
||||
timeout: 10
|
||||
|
||||
@ -579,7 +579,7 @@ cameras:
|
||||
# Required: Begin automatically tracking an object when it enters any of the listed zones.
|
||||
required_zones:
|
||||
- zone_name
|
||||
# Required: Name of ONVIF camera preset to return to when tracking is over.
|
||||
# Required: Name of ONVIF preset in camera's firmware to return to when tracking is over. (default: shown below)
|
||||
return_preset: preset_name
|
||||
# Optional: Seconds to delay before returning to preset. (default: shown below)
|
||||
timeout: 10
|
||||
|
||||
@ -25,8 +25,8 @@ Frigate uses the following locations for read/write operations in the container.
|
||||
- `/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/exports`: Storage for clips and timelapses that have been exported via the WebUI or API.
|
||||
- `/tmp/cache`: Cache location for recording segments. Initial recordings are written here before being checked and converted to mp4 and moved to the recordings folder.
|
||||
- `/dev/shm`: It is not recommended to modify this directory or map it with docker. This is the location for raw decoded frames in shared memory and it's size is impacted by the `shm-size` calculations below.
|
||||
- `/tmp/cache`: Cache location for recording segments. Initial recordings are written here before being checked and converted to mp4 and moved to the recordings folder. Segments generated via the `clip.mp4` endpoints are also concatenated and processed here. It is recommended to use a [`tmpfs`](https://docs.docker.com/storage/tmpfs/) mount for this.
|
||||
- `/dev/shm`: Internal cache for raw decoded frames in shared memory. It is not recommended to modify this directory or map it with docker. The minimum size is impacted by the `shm-size` calculations below.
|
||||
|
||||
#### Common docker compose storage configurations
|
||||
|
||||
@ -51,7 +51,7 @@ services:
|
||||
|
||||
Frigate utilizes shared memory to store frames during processing. The default `shm-size` provided by Docker is **64MB**.
|
||||
|
||||
The default shm size of **64MB** is fine for setups with **2 cameras** detecting at **720p**. If Frigate is exiting with "Bus error" messages, it is likely because you have too many high resolution cameras and you need to specify a higher shm size.
|
||||
The default shm size of **64MB** is fine for setups with **2 cameras** detecting at **720p**. If Frigate is exiting with "Bus error" messages, it is likely because you have too many high resolution cameras and you need to specify a higher shm size, using [`--shm-size`](https://docs.docker.com/engine/reference/run/#runtime-constraints-on-resources) (or [`service.shm_size`](https://docs.docker.com/compose/compose-file/compose-file-v2/#shm_size) in docker-compose).
|
||||
|
||||
The Frigate container also stores logs in shm, which can take up to **30MB**, so make sure to take this into account in your math as well.
|
||||
|
||||
|
||||
38
docs/docs/troubleshooting/recordings.md
Normal file
38
docs/docs/troubleshooting/recordings.md
Normal file
@ -0,0 +1,38 @@
|
||||
---
|
||||
id: recordings
|
||||
title: Troubleshooting Recordings
|
||||
---
|
||||
|
||||
## `WARNING : Unable to keep up with recording segments in cache for {camera}. Keeping the 5 most recent segments out of 6 and discarding the rest...`
|
||||
|
||||
This error can be caused by a number of different issues. The first step in troubleshooting is to enable debug logging for recording, this will enable logging showing how long it takes for recordings to be moved from RAM cache to the disk.
|
||||
|
||||
```yaml
|
||||
logger:
|
||||
logs:
|
||||
frigate.record.maintainer: debug
|
||||
```
|
||||
|
||||
This will include logs like:
|
||||
|
||||
```
|
||||
DEBUG : Copied /media/frigate/recordings/{segment_path} in 0.2 seconds.
|
||||
```
|
||||
|
||||
It is important to let this run until the errors begin to happen, to confirm that there is not a slow down in the disk at the time of the error.
|
||||
|
||||
### Copy Times > 1 second
|
||||
|
||||
If the storage is too slow to keep up with the recordings then the maintainer will fall behind and purge the oldest recordings to ensure the cache does not fill up causing a crash. In this case it is important to diagnose why the copy times are slow.
|
||||
|
||||
#### Check Storage Type
|
||||
|
||||
Mounting a network share is a popular option for storing Recordings, but this can lead to reduced copy times and cause problems. Some users have found that using `NFS` instead of `SMB` considerably decreased the copy times and fixed the issue. It is also important to ensure that the network connection between the device running Frigate and the network share is stable and fast.
|
||||
|
||||
#### Check mount options
|
||||
|
||||
Some users found that mounting a drive via `fstab` with the `sync` option caused dramatically reduce performance and led to this issue. Using `async` instead greatly reduced copy times.
|
||||
|
||||
### Copy Times < 1 second
|
||||
|
||||
If the storage is working quickly then this error may be caused by CPU load on the machine being too high for Frigate to have the resources to keep up. Try temporarily shutting down other services to see if the issue improves.
|
||||
@ -58,6 +58,7 @@ module.exports = {
|
||||
],
|
||||
Troubleshooting: [
|
||||
"troubleshooting/faqs",
|
||||
"troubleshooting/recordings",
|
||||
],
|
||||
Development: [
|
||||
"development/contributing",
|
||||
|
||||
@ -52,9 +52,6 @@ class Dispatcher:
|
||||
self.ptz_metrics = ptz_metrics
|
||||
self.comms = communicators
|
||||
|
||||
for comm in self.comms:
|
||||
comm.subscribe(self._receive)
|
||||
|
||||
self._camera_settings_handlers: dict[str, Callable] = {
|
||||
"audio": self._on_audio_command,
|
||||
"detect": self._on_detect_command,
|
||||
@ -67,6 +64,9 @@ class Dispatcher:
|
||||
"snapshots": self._on_snapshots_command,
|
||||
}
|
||||
|
||||
for comm in self.comms:
|
||||
comm.subscribe(self._receive)
|
||||
|
||||
def _receive(self, topic: str, payload: str) -> None:
|
||||
"""Handle receiving of payload from communicators."""
|
||||
if topic.endswith("set"):
|
||||
|
||||
@ -444,6 +444,7 @@ class AudioConfig(FrigateBaseModel):
|
||||
enabled_in_config: Optional[bool] = Field(
|
||||
title="Keep track of original state of audio detection."
|
||||
)
|
||||
num_threads: int = Field(default=2, title="Number of detection threads", ge=1)
|
||||
|
||||
|
||||
class BirdseyeModeEnum(str, Enum):
|
||||
|
||||
@ -89,12 +89,13 @@ def listen_to_audio(
|
||||
|
||||
|
||||
class AudioTfl:
|
||||
def __init__(self, stop_event: mp.Event):
|
||||
def __init__(self, stop_event: mp.Event, num_threads=2):
|
||||
self.stop_event = stop_event
|
||||
self.labels = load_labels("/audio-labelmap.txt")
|
||||
self.num_threads = num_threads
|
||||
self.labels = load_labels("/audio-labelmap.txt", prefill=521)
|
||||
self.interpreter = Interpreter(
|
||||
model_path="/cpu_audio_model.tflite",
|
||||
num_threads=2,
|
||||
num_threads=self.num_threads,
|
||||
)
|
||||
|
||||
self.interpreter.allocate_tensors()
|
||||
@ -117,7 +118,7 @@ class AudioTfl:
|
||||
count = len(scores)
|
||||
|
||||
for i in range(count):
|
||||
if scores[i] < 0.4 or i == 20:
|
||||
if scores[i] < AUDIO_MIN_CONFIDENCE or i == 20:
|
||||
break
|
||||
detections[i] = [
|
||||
class_ids[i],
|
||||
@ -164,7 +165,7 @@ class AudioEventMaintainer(threading.Thread):
|
||||
self.inter_process_communicator = inter_process_communicator
|
||||
self.detections: dict[dict[str, any]] = feature_metrics
|
||||
self.stop_event = stop_event
|
||||
self.detector = AudioTfl(stop_event)
|
||||
self.detector = AudioTfl(stop_event, self.config.audio.num_threads)
|
||||
self.shape = (int(round(AUDIO_DURATION * AUDIO_SAMPLE_RATE)),)
|
||||
self.chunk_size = int(round(AUDIO_DURATION * AUDIO_SAMPLE_RATE * 2))
|
||||
self.logger = logging.getLogger(f"audio.{self.config.name}")
|
||||
|
||||
@ -168,6 +168,7 @@ class EventCleanup(threading.Thread):
|
||||
camera,
|
||||
has_snapshot,
|
||||
has_clip,
|
||||
end_time,
|
||||
row_number() over (
|
||||
partition by label, camera, round(start_time/5,0)*5
|
||||
order by end_time-start_time desc
|
||||
@ -176,7 +177,7 @@ class EventCleanup(threading.Thread):
|
||||
)
|
||||
|
||||
select distinct id, camera, has_snapshot, has_clip from grouped_events
|
||||
where copy_number > 1;"""
|
||||
where copy_number > 1 and end_time not null;"""
|
||||
|
||||
duplicate_events = Event.raw(duplicate_query)
|
||||
for event in duplicate_events:
|
||||
|
||||
@ -29,6 +29,7 @@ from frigate.util.image import (
|
||||
calculate_region,
|
||||
draw_box_with_label,
|
||||
draw_timestamp,
|
||||
is_label_printable,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -509,13 +510,21 @@ class CameraState:
|
||||
|
||||
# draw the bounding boxes on the frame
|
||||
box = obj["box"]
|
||||
text = (
|
||||
obj["label"]
|
||||
if (
|
||||
not obj.get("sub_label")
|
||||
or not is_label_printable(obj["sub_label"][0])
|
||||
)
|
||||
else obj["sub_label"][0]
|
||||
)
|
||||
draw_box_with_label(
|
||||
frame_copy,
|
||||
box[0],
|
||||
box[1],
|
||||
box[2],
|
||||
box[3],
|
||||
obj["label"],
|
||||
text,
|
||||
f"{obj['score']:.0%} {int(obj['area'])}",
|
||||
thickness=thickness,
|
||||
color=color,
|
||||
|
||||
@ -210,7 +210,7 @@ class BroadcastThread(threading.Thread):
|
||||
ws.send(buf, binary=True)
|
||||
except ValueError:
|
||||
pass
|
||||
except ConnectionResetError as e:
|
||||
except (BrokenPipeError, ConnectionResetError) as e:
|
||||
logger.debug(f"Websocket unexpectedly closed {e}")
|
||||
elif self.converter.process.poll() is not None:
|
||||
break
|
||||
|
||||
@ -119,10 +119,16 @@ class OnvifController:
|
||||
move_request.Translation.PanTilt.space = ptz_config["Spaces"][
|
||||
"RelativePanTiltTranslationSpace"
|
||||
][fov_space_id]["URI"]
|
||||
|
||||
try:
|
||||
if zoom_space_id is not None:
|
||||
move_request.Translation.Zoom.space = ptz_config["Spaces"][
|
||||
"RelativeZoomTranslationSpace"
|
||||
][0]["URI"]
|
||||
except Exception:
|
||||
# camera does not support relative zoom
|
||||
pass
|
||||
|
||||
if move_request.Speed is None:
|
||||
move_request.Speed = ptz.GetStatus({"ProfileToken": profile.token}).Position
|
||||
self.cams[camera_name]["relative_move_request"] = move_request
|
||||
|
||||
@ -134,7 +134,7 @@ def get_ffmpeg_arg_list(arg: Any) -> list:
|
||||
return arg if isinstance(arg, list) else shlex.split(arg)
|
||||
|
||||
|
||||
def load_labels(path, encoding="utf-8"):
|
||||
def load_labels(path, encoding="utf-8", prefill=91):
|
||||
"""Loads labels from file (with or without index numbers).
|
||||
Args:
|
||||
path: path to label file.
|
||||
@ -143,7 +143,7 @@ def load_labels(path, encoding="utf-8"):
|
||||
Dictionary mapping indices to labels.
|
||||
"""
|
||||
with open(path, "r", encoding=encoding) as f:
|
||||
labels = {index: "unknown" for index in range(91)}
|
||||
labels = {index: "unknown" for index in range(prefill)}
|
||||
lines = f.readlines()
|
||||
if not lines:
|
||||
return {}
|
||||
|
||||
@ -4,6 +4,7 @@ import datetime
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from multiprocessing import shared_memory
|
||||
from string import printable
|
||||
from typing import AnyStr, Optional
|
||||
|
||||
import cv2
|
||||
@ -154,6 +155,11 @@ def draw_box_with_label(
|
||||
)
|
||||
|
||||
|
||||
def is_label_printable(label) -> bool:
|
||||
"""Check if label is printable."""
|
||||
return not bool(set(label) - set(printable))
|
||||
|
||||
|
||||
def calculate_region(frame_shape, xmin, ymin, xmax, ymax, model_size, multiplier=2):
|
||||
# size is the longest edge and divisible by 4
|
||||
size = int((max(xmax - xmin, ymax - ymin) * multiplier) // 4 * 4)
|
||||
|
||||
@ -5,6 +5,8 @@ import { ArrowDropdown } from '../icons/ArrowDropdown';
|
||||
import Heading from './Heading';
|
||||
import Button from './Button';
|
||||
import CameraIcon from '../icons/Camera';
|
||||
import SpeakerIcon from '../icons/Speaker';
|
||||
import useSWR from 'swr';
|
||||
|
||||
export default function MultiSelect({ className, title, options, selection, onToggle, onShowAll, onSelectSingle }) {
|
||||
const popupRef = useRef(null);
|
||||
@ -18,7 +20,7 @@ export default function MultiSelect({ className, title, options, selection, onTo
|
||||
};
|
||||
|
||||
const menuHeight = Math.round(window.innerHeight * 0.55);
|
||||
|
||||
const { data: config } = useSWR('config');
|
||||
return (
|
||||
<div className={`${className} p-2`} ref={popupRef}>
|
||||
<div className="flex justify-between min-w-[120px]" onClick={() => setState({ showMenu: true })}>
|
||||
@ -59,7 +61,8 @@ export default function MultiSelect({ className, title, options, selection, onTo
|
||||
className="max-h-[35px] mx-2"
|
||||
onClick={() => onSelectSingle(item)}
|
||||
>
|
||||
<CameraIcon />
|
||||
{ (title === "Labels" && config.audio.listen.includes(item)) ? ( <SpeakerIcon /> ) : ( <CameraIcon /> ) }
|
||||
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { h } from 'preact';
|
||||
import { memo } from 'preact/compat';
|
||||
|
||||
export function Snapshot({ className = 'h-6 w-6', stroke = 'currentColor', onClick = () => {} }) {
|
||||
export function Audio({ className = 'h-6 w-6', stroke = 'currentColor', onClick = () => {} }) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -33,4 +33,4 @@ export function Snapshot({ className = 'h-6 w-6', stroke = 'currentColor', onCli
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(Snapshot);
|
||||
export default memo(Audio);
|
||||
|
||||
20
web/src/icons/Speaker.jsx
Normal file
20
web/src/icons/Speaker.jsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { h } from 'preact';
|
||||
import { memo } from 'preact/compat';
|
||||
|
||||
|
||||
export function Speaker({
|
||||
className = 'h-7 w-7',
|
||||
stroke = 'currentColor',
|
||||
onClick = () => {},
|
||||
}) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 32"
|
||||
|
||||
stroke={stroke}
|
||||
onClick={onClick}
|
||||
className={className}>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" strokeWidth="2" d="M19.114 5.636a9 9 0 010 12.728M16.463 8.288a5.25 5.25 0 010 7.424M6.75 8.25l4.72-4.72a.75.75 0 011.28.53v15.88a.75.75 0 01-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 012.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75z"/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
export default memo(Speaker);
|
||||
Loading…
Reference in New Issue
Block a user