forked from Github/frigate
Compare commits
31 Commits
v0.13.0-be
...
v0.13.0-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
614a36af9f | ||
|
|
a0bc3a3626 | ||
|
|
18062eca06 | ||
|
|
1dc42d2904 | ||
|
|
500d369c50 | ||
|
|
3dd0192fe6 | ||
|
|
1eb5105b24 | ||
|
|
463865db55 | ||
|
|
ea247ca816 | ||
|
|
8864e33d1c | ||
|
|
934b16723b | ||
|
|
fc186e4d5f | ||
|
|
7d157dfeb0 | ||
|
|
977eef9138 | ||
|
|
678f1201c6 | ||
|
|
4879de263b | ||
|
|
c6208b266b | ||
|
|
2da99c2308 | ||
|
|
9ac40cd953 | ||
|
|
7522bb6fab | ||
|
|
7b520e8a9d | ||
|
|
cadb1a6a5b | ||
|
|
97c15f7ef3 | ||
|
|
9fa70c3455 | ||
|
|
8c7f6d4a76 | ||
|
|
266b4099b5 | ||
|
|
a1e68a62d0 | ||
|
|
8a010fc1f5 | ||
|
|
563fdec211 | ||
|
|
3457dcddfe | ||
|
|
57a06d2220 |
@@ -42,7 +42,6 @@
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-python.vscode-pylance",
|
||||
"ms-python.black-formatter",
|
||||
"visualstudioexptteam.vscodeintellicode",
|
||||
"mhutchie.git-graph",
|
||||
"ms-azuretools.vscode-docker",
|
||||
@@ -53,13 +52,10 @@
|
||||
"csstools.postcss",
|
||||
"blanu.vscode-styled-jsx",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"ms-python.isort",
|
||||
"charliermarsh.ruff"
|
||||
],
|
||||
"settings": {
|
||||
"remote.autoForwardPorts": false,
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.linting.enabled": true,
|
||||
"python.formatting.provider": "none",
|
||||
"python.languageServer": "Pylance",
|
||||
"editor.formatOnPaste": false,
|
||||
@@ -72,7 +68,7 @@
|
||||
"eslint.workingDirectories": ["./web"],
|
||||
"isort.args": ["--settings-path=./pyproject.toml"],
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "ms-python.black-formatter",
|
||||
"editor.defaultFormatter": "charliermarsh.ruff",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": true,
|
||||
|
||||
9
.github/workflows/pull_request.yml
vendored
9
.github/workflows/pull_request.yml
vendored
@@ -72,13 +72,10 @@ jobs:
|
||||
run: |
|
||||
python3 -m pip install -U pip
|
||||
python3 -m pip install -r docker/main/requirements-dev.txt
|
||||
- name: Check black
|
||||
- name: Check formatting
|
||||
run: |
|
||||
black --check --diff frigate migrations docker *.py
|
||||
- name: Check isort
|
||||
run: |
|
||||
isort --check --diff frigate migrations docker *.py
|
||||
- name: Check ruff
|
||||
ruff format --check --diff frigate migrations docker *.py
|
||||
- name: Check lint
|
||||
run: |
|
||||
ruff check frigate migrations docker *.py
|
||||
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -32,6 +32,6 @@ jobs:
|
||||
VERSION_TAG=${BASE}:${CLEAN_VERSION}
|
||||
PULL_TAG=${BASE}:${BUILD_TAG}
|
||||
docker run --rm -v $HOME/.docker/config.json:/config.json quay.io/skopeo/stable:latest copy --authfile /config.json --multi-arch all docker://${PULL_TAG} docker://${VERSION_TAG}
|
||||
for variant in standard-amd64 tensorrt tensorrt-jp4 tensorrt-jp5 rk; do
|
||||
for variant in standard-arm64 tensorrt tensorrt-jp4 tensorrt-jp5 rk; do
|
||||
docker run --rm -v $HOME/.docker/config.json:/config.json quay.io/skopeo/stable:latest copy --authfile /config.json --multi-arch all docker://${PULL_TAG}-${variant} docker://${VERSION_TAG}-${variant}
|
||||
done
|
||||
|
||||
@@ -14,13 +14,14 @@ services:
|
||||
dockerfile: docker/main/Dockerfile
|
||||
# Use target devcontainer-trt for TensorRT dev
|
||||
target: devcontainer
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: 1
|
||||
capabilities: [gpu]
|
||||
## Uncomment this block for nvidia gpu support
|
||||
# deploy:
|
||||
# resources:
|
||||
# reservations:
|
||||
# devices:
|
||||
# - driver: nvidia
|
||||
# count: 1
|
||||
# capabilities: [gpu]
|
||||
environment:
|
||||
YOLO_MODELS: yolov7-320
|
||||
devices:
|
||||
|
||||
@@ -33,7 +33,7 @@ RUN --mount=type=tmpfs,target=/tmp --mount=type=tmpfs,target=/var/cache/apt \
|
||||
FROM scratch AS go2rtc
|
||||
ARG TARGETARCH
|
||||
WORKDIR /rootfs/usr/local/go2rtc/bin
|
||||
ADD --link --chmod=755 "https://github.com/AlexxIT/go2rtc/releases/download/v1.8.2/go2rtc_linux_${TARGETARCH}" go2rtc
|
||||
ADD --link --chmod=755 "https://github.com/AlexxIT/go2rtc/releases/download/v1.8.4/go2rtc_linux_${TARGETARCH}" go2rtc
|
||||
|
||||
|
||||
####
|
||||
@@ -215,13 +215,13 @@ COPY docker/main/fake_frigate_run /etc/s6-overlay/s6-rc.d/frigate/run
|
||||
RUN mkdir -p /opt/frigate \
|
||||
&& ln -svf /workspace/frigate/frigate /opt/frigate/frigate
|
||||
|
||||
# Install Node 16
|
||||
RUN apt-get update \
|
||||
&& apt-get install wget -y \
|
||||
&& wget -qO- https://deb.nodesource.com/setup_16.x | bash - \
|
||||
&& apt-get install -y nodejs \
|
||||
# Install Node 20
|
||||
RUN curl -SLO https://deb.nodesource.com/nsolid_setup_deb.sh && \
|
||||
chmod 500 nsolid_setup_deb.sh && \
|
||||
./nsolid_setup_deb.sh 20 && \
|
||||
apt-get install nodejs -y \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& npm install -g npm@9
|
||||
&& npm install -g npm@10
|
||||
|
||||
WORKDIR /workspace/frigate
|
||||
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
black == 23.10.*
|
||||
isort
|
||||
ruff
|
||||
|
||||
@@ -23,6 +23,7 @@ scipy == 1.11.*
|
||||
norfair == 2.2.*
|
||||
setproctitle == 1.3.*
|
||||
ws4py == 0.5.*
|
||||
unidecode == 1.3.*
|
||||
# Openvino Library - Custom built with MYRIAD support
|
||||
openvino @ https://github.com/NateMeyer/openvino-wheels/releases/download/multi-arch_2022.3.1/openvino-2022.3.1-1-cp39-cp39-manylinux_2_31_x86_64.whl; platform_machine == 'x86_64'
|
||||
openvino @ https://github.com/NateMeyer/openvino-wheels/releases/download/multi-arch_2022.3.1/openvino-2022.3.1-1-cp39-cp39-linux_aarch64.whl; platform_machine == 'aarch64'
|
||||
|
||||
@@ -45,8 +45,13 @@ function get_ip_and_port_from_supervisor() {
|
||||
|
||||
export LIBAVFORMAT_VERSION_MAJOR=$(ffmpeg -version | grep -Po 'libavformat\W+\K\d+')
|
||||
|
||||
if [[ -f "/dev/shm/go2rtc.yaml" ]]; then
|
||||
echo "[INFO] Removing stale config from last run..."
|
||||
rm /dev/shm/go2rtc.yaml
|
||||
fi
|
||||
|
||||
if [[ ! -f "/dev/shm/go2rtc.yaml" ]]; then
|
||||
echo "[INFO] Preparing go2rtc config..."
|
||||
echo "[INFO] Preparing new go2rtc config..."
|
||||
|
||||
if [[ -n "${SUPERVISOR_TOKEN:-}" ]]; then
|
||||
# Running as a Home Assistant add-on, infer the IP address and port
|
||||
@@ -54,6 +59,8 @@ if [[ ! -f "/dev/shm/go2rtc.yaml" ]]; then
|
||||
fi
|
||||
|
||||
python3 /usr/local/go2rtc/create_config.py
|
||||
else
|
||||
echo "[WARNING] Unable to remove existing go2rtc config. Changes made to your frigate config file may not be recognized. Please remove the /dev/shm/go2rtc.yaml from your docker host manually."
|
||||
fi
|
||||
|
||||
readonly config_path="/config"
|
||||
|
||||
@@ -113,6 +113,20 @@ if int(os.environ["LIBAVFORMAT_VERSION_MAJOR"]) < 59:
|
||||
"rtsp"
|
||||
] = "-fflags nobuffer -flags low_delay -stimeout 5000000 -user_agent go2rtc/ffmpeg -rtsp_transport tcp -i {input}"
|
||||
|
||||
# add hardware acceleration presets for rockchip devices
|
||||
# may be removed if frigate uses a go2rtc version that includes these presets
|
||||
if go2rtc_config.get("ffmpeg") is None:
|
||||
go2rtc_config["ffmpeg"] = {
|
||||
"h264/rk": "-c:v h264_rkmpp_encoder -g 50 -bf 0",
|
||||
"h265/rk": "-c:v hevc_rkmpp_encoder -g 50 -bf 0",
|
||||
}
|
||||
else:
|
||||
if go2rtc_config["ffmpeg"].get("h264/rk") is None:
|
||||
go2rtc_config["ffmpeg"]["h264/rk"] = "-c:v h264_rkmpp_encoder -g 50 -bf 0"
|
||||
|
||||
if go2rtc_config["ffmpeg"].get("h265/rk") is None:
|
||||
go2rtc_config["ffmpeg"]["h265/rk"] = "-c:v hevc_rkmpp_encoder -g 50 -bf 0"
|
||||
|
||||
for name in go2rtc_config.get("streams", {}):
|
||||
stream = go2rtc_config["streams"][name]
|
||||
|
||||
|
||||
@@ -6,12 +6,9 @@ ARG DEBIAN_FRONTEND=noninteractive
|
||||
FROM wheels as rk-wheels
|
||||
COPY docker/main/requirements-wheels.txt /requirements-wheels.txt
|
||||
COPY docker/rockchip/requirements-wheels-rk.txt /requirements-wheels-rk.txt
|
||||
RUN sed -i "/https/d" /requirements-wheels.txt
|
||||
RUN sed -i "/https:\/\//d" /requirements-wheels.txt
|
||||
RUN pip3 wheel --wheel-dir=/rk-wheels -c /requirements-wheels.txt -r /requirements-wheels-rk.txt
|
||||
|
||||
FROM wget as rk-libs
|
||||
RUN wget -qO librknnrt.so https://github.com/MarcA711/rknpu2/raw/master/runtime/RK3588/Linux/librknn_api/aarch64/librknnrt.so
|
||||
|
||||
FROM deps AS rk-deps
|
||||
ARG TARGETARCH
|
||||
|
||||
@@ -20,5 +17,16 @@ RUN --mount=type=bind,from=rk-wheels,source=/rk-wheels,target=/deps/rk-wheels \
|
||||
|
||||
WORKDIR /opt/frigate/
|
||||
COPY --from=rootfs / /
|
||||
COPY --from=rk-libs /rootfs/librknnrt.so /usr/lib/
|
||||
COPY docker/rockchip/yolov8n-320x320.rknn /models/
|
||||
|
||||
ADD https://github.com/MarcA711/rknpu2/releases/download/v1.5.2/librknnrt_rk356x.so /usr/lib/
|
||||
ADD https://github.com/MarcA711/rknpu2/releases/download/v1.5.2/librknnrt_rk3588.so /usr/lib/
|
||||
|
||||
ADD https://github.com/MarcA711/rknn-models/releases/download/v1.5.2-rk3562/yolov8n-320x320-rk3562.rknn /models/rknn/
|
||||
ADD https://github.com/MarcA711/rknn-models/releases/download/v1.5.2-rk3566/yolov8n-320x320-rk3566.rknn /models/rknn/
|
||||
ADD https://github.com/MarcA711/rknn-models/releases/download/v1.5.2-rk3568/yolov8n-320x320-rk3568.rknn /models/rknn/
|
||||
ADD https://github.com/MarcA711/rknn-models/releases/download/v1.5.2-rk3588/yolov8n-320x320-rk3588.rknn /models/rknn/
|
||||
|
||||
RUN rm -rf /usr/lib/btbn-ffmpeg/bin/ffmpeg
|
||||
RUN rm -rf /usr/lib/btbn-ffmpeg/bin/ffprobe
|
||||
ADD --chmod=111 https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/download/6.0-1/ffmpeg /usr/lib/btbn-ffmpeg/bin/
|
||||
ADD --chmod=111 https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/download/6.0-1/ffprobe /usr/lib/btbn-ffmpeg/bin/
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
hide-warnings == 0.17
|
||||
rknn-toolkit-lite2 @ https://github.com/MarcA711/rknn-toolkit2/raw/master/rknn_toolkit_lite2/packages/rknn_toolkit_lite2-1.5.2-cp39-cp39-linux_aarch64.whl
|
||||
rknn-toolkit-lite2 @ https://github.com/MarcA711/rknn-toolkit2/releases/download/v1.5.2/rknn_toolkit_lite2-1.5.2-cp39-cp39-linux_aarch64.whl
|
||||
Binary file not shown.
@@ -120,7 +120,7 @@ NOTE: The folder that is mapped from the host needs to be the folder that contai
|
||||
|
||||
## Custom go2rtc version
|
||||
|
||||
Frigate currently includes go2rtc v1.8.2, there may be certain cases where you want to run a different version of go2rtc.
|
||||
Frigate currently includes go2rtc v1.8.4, there may be certain cases where you want to run a different version of go2rtc.
|
||||
|
||||
To do this:
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ go2rtc:
|
||||
- rtspx://192.168.1.1:7441/abcdefghijk
|
||||
```
|
||||
|
||||
[See the go2rtc docs for more information](https://github.com/AlexxIT/go2rtc/tree/v1.8.2#source-rtsp)
|
||||
[See the go2rtc docs for more information](https://github.com/AlexxIT/go2rtc/tree/v1.8.4#source-rtsp)
|
||||
|
||||
In the Unifi 2.0 update Unifi Protect Cameras had a change in audio sample rate which causes issues for ffmpeg. The input rate needs to be set for record and rtmp if used directly with unifi protect.
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ This list of working and non-working PTZ cameras is based on user feedback.
|
||||
| Reolink 511WA | ✅ | ❌ | Zoom only |
|
||||
| Reolink E1 Pro | ✅ | ❌ | |
|
||||
| Reolink E1 Zoom | ✅ | ❌ | |
|
||||
| Reolink RLC-823A 16x | ✅ | ❌ | |
|
||||
| Sunba 405-D20X | ✅ | ❌ | |
|
||||
| Tapo C200 | ✅ | ❌ | Incomplete ONVIF support |
|
||||
| Tapo C210 | ❌ | ❌ | Incomplete ONVIF support |
|
||||
|
||||
@@ -14,6 +14,7 @@ See [the hwaccel docs](/configuration/hardware_acceleration.md) for more info on
|
||||
| Preset | Usage | Other Notes |
|
||||
| --------------------- | ------------------------------ | ----------------------------------------------------- |
|
||||
| preset-rpi-64-h264 | 64 bit Rpi with h264 stream | |
|
||||
| preset-rpi-64-h265 | 64 bit Rpi with h265 stream | |
|
||||
| preset-vaapi | Intel & AMD VAAPI | Check hwaccel docs to ensure correct driver is chosen |
|
||||
| preset-intel-qsv-h264 | Intel QSV with h264 stream | If issues occur recommend using vaapi preset instead |
|
||||
| preset-intel-qsv-h265 | Intel QSV with h265 stream | If issues occur recommend using vaapi preset instead |
|
||||
@@ -22,6 +23,8 @@ See [the hwaccel docs](/configuration/hardware_acceleration.md) for more info on
|
||||
| preset-nvidia-mjpeg | Nvidia GPU with mjpeg stream | Recommend restreaming mjpeg and using nvidia-h264 |
|
||||
| preset-jetson-h264 | Nvidia Jetson with h264 stream | |
|
||||
| preset-jetson-h265 | Nvidia Jetson with h265 stream | |
|
||||
| preset-rk-h264 | Rockchip MPP with h264 stream | Use image with *-rk suffix and privileged mode |
|
||||
| preset-rk-h265 | Rockchip MPP with h265 stream | Use image with *-rk suffix and privileged mode |
|
||||
|
||||
### Input Args Presets
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ id: hardware_acceleration
|
||||
title: Hardware Acceleration
|
||||
---
|
||||
|
||||
# Hardware Acceleration
|
||||
|
||||
It is recommended to update your configuration to enable hardware accelerated decoding in ffmpeg. Depending on your system, these parameters may not be compatible. More information on hardware accelerated decoding for ffmpeg can be found here: https://trac.ffmpeg.org/wiki/HWAccelIntro
|
||||
|
||||
# Officially Supported
|
||||
@@ -13,8 +15,13 @@ Ensure you increase the allocated RAM for your GPU to at least 128 (raspi-config
|
||||
**NOTICE**: If you are using the addon, you may need to turn off `Protection mode` for hardware acceleration.
|
||||
|
||||
```yaml
|
||||
# if you want to decode a h264 stream
|
||||
ffmpeg:
|
||||
hwaccel_args: preset-rpi-64-h264
|
||||
|
||||
# if you want to decode a h265 (hevc) stream
|
||||
ffmpeg:
|
||||
hwaccel_args: preset-rpi-64-h265
|
||||
```
|
||||
|
||||
:::note
|
||||
@@ -23,10 +30,10 @@ If running Frigate in docker, you either need to run in priviliged mode or be su
|
||||
|
||||
```yaml
|
||||
docker run -d \
|
||||
--name frigate \
|
||||
...
|
||||
--device /dev/video10 \
|
||||
ghcr.io/blakeblackshear/frigate:stable
|
||||
--name frigate \
|
||||
...
|
||||
--device /dev/video10 \
|
||||
ghcr.io/blakeblackshear/frigate:stable
|
||||
```
|
||||
|
||||
:::
|
||||
@@ -246,7 +253,7 @@ These instructions were originally based on the [Jellyfin documentation](https:/
|
||||
|
||||
# Community Supported
|
||||
|
||||
## NVIDIA Jetson (Orin AGX, Orin NX, Orin Nano*, Xavier AGX, Xavier NX, TX2, TX1, Nano)
|
||||
## NVIDIA Jetson (Orin AGX, Orin NX, Orin Nano\*, Xavier AGX, Xavier NX, TX2, TX1, Nano)
|
||||
|
||||
A separate set of docker images is available that is based on Jetpack/L4T. They comes with an `ffmpeg` build
|
||||
with codecs that use the Jetson's dedicated media engine. If your Jetson host is running Jetpack 4.6, use the
|
||||
@@ -319,3 +326,57 @@ ffmpeg:
|
||||
If everything is working correctly, you should see a significant reduction in ffmpeg CPU load and power consumption.
|
||||
Verify that hardware decoding is working by running `jtop` (`sudo pip3 install -U jetson-stats`), which should show
|
||||
that NVDEC/NVDEC1 are in use.
|
||||
|
||||
## Rockchip platform
|
||||
|
||||
Hardware accelerated video de-/encoding is supported on all Rockchip SoCs.
|
||||
|
||||
### Setup
|
||||
|
||||
Use a frigate docker image with `-rk` suffix and enable privileged mode by adding the `--privileged` flag to your docker run command or `privileged: true` to your `docker-compose.yml` file.
|
||||
|
||||
### Configuration
|
||||
|
||||
Add one of the following ffmpeg presets to your `config.yaml` to enable hardware acceleration:
|
||||
|
||||
```yaml
|
||||
# if you try to decode a h264 encoded stream
|
||||
ffmpeg:
|
||||
hwaccel_args: preset-rk-h264
|
||||
|
||||
# if you try to decode a h265 (hevc) encoded stream
|
||||
ffmpeg:
|
||||
hwaccel_args: preset-rk-h265
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
Make sure that your SoC supports hardware acceleration for your input stream. For example, if your camera streams with h265 encoding and a 4k resolution, your SoC must be able to de- and encode h265 with a 4k resolution or higher. If you are unsure whether your SoC meets the requirements, take a look at the datasheet.
|
||||
|
||||
:::
|
||||
|
||||
### go2rtc presets for hardware accelerated transcoding
|
||||
|
||||
If your input stream is to be transcoded using hardware acceleration, there are these presets for go2rtc: `h264/rk` and `h265/rk`. You can use them this way:
|
||||
|
||||
```
|
||||
go2rtc:
|
||||
streams:
|
||||
Cam_h264: ffmpeg:rtsp://username:password@192.168.1.123/av_stream/ch0#video=h264/rk
|
||||
Cam_h265: ffmpeg:rtsp://username:password@192.168.1.123/av_stream/ch0#video=h265/rk
|
||||
```
|
||||
|
||||
:::warning
|
||||
|
||||
The go2rtc docs may suggest the following configuration:
|
||||
|
||||
```
|
||||
go2rtc:
|
||||
streams:
|
||||
Cam_h264: ffmpeg:rtsp://username:password@192.168.1.123/av_stream/ch0#video=h264#hardware=rk
|
||||
Cam_h265: ffmpeg:rtsp://username:password@192.168.1.123/av_stream/ch0#video=h265#hardware=rk
|
||||
```
|
||||
|
||||
However, this does not currently work.
|
||||
|
||||
:::
|
||||
|
||||
@@ -25,22 +25,9 @@ cameras:
|
||||
|
||||
VSCode (and VSCode addon) supports the JSON schemas which will automatically validate the config. This can be added by adding `# yaml-language-server: $schema=http://frigate_host:5000/api/config/schema.json` to the top of the config file. `frigate_host` being the IP address of Frigate or `ccab4aaf-frigate` if running in the addon.
|
||||
|
||||
### Full configuration reference:
|
||||
### Environment Variable Substitution
|
||||
|
||||
:::caution
|
||||
|
||||
It is not recommended to copy this full configuration file. Only specify values that are different from the defaults. Configuration options and default values may change in future versions.
|
||||
|
||||
:::
|
||||
|
||||
**Note:** The following values will be replaced at runtime by using environment variables
|
||||
|
||||
- `{FRIGATE_MQTT_USER}`
|
||||
- `{FRIGATE_MQTT_PASSWORD}`
|
||||
- `{FRIGATE_RTSP_USER}`
|
||||
- `{FRIGATE_RTSP_PASSWORD}`
|
||||
|
||||
for example:
|
||||
Frigate supports the use of environment variables starting with `FRIGATE_` **only** where specifically indicated in the configuration reference below. For example, the following values can be replaced at runtime by using environment variables:
|
||||
|
||||
```yaml
|
||||
mqtt:
|
||||
@@ -60,6 +47,14 @@ onvif:
|
||||
password: "{FRIGATE_RTSP_PASSWORD}"
|
||||
```
|
||||
|
||||
### Full configuration reference:
|
||||
|
||||
:::caution
|
||||
|
||||
It is not recommended to copy this full configuration file. Only specify values that are different from the defaults. Configuration options and default values may change in future versions.
|
||||
|
||||
:::
|
||||
|
||||
```yaml
|
||||
mqtt:
|
||||
# Optional: Enable mqtt server (default: shown below)
|
||||
@@ -350,8 +345,8 @@ record:
|
||||
# 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
|
||||
expire_interval: 60
|
||||
# Optional: Sync recordings with disk on startup (default: shown below).
|
||||
sync_on_startup: False
|
||||
# Optional: Sync recordings with disk on startup and once a day (default: shown below).
|
||||
sync_recordings: False
|
||||
# Optional: Retention settings for recording
|
||||
retain:
|
||||
# Optional: Number of days to retain recordings regardless of events (default: shown below)
|
||||
@@ -438,7 +433,7 @@ rtmp:
|
||||
enabled: False
|
||||
|
||||
# Optional: Restream configuration
|
||||
# Uses https://github.com/AlexxIT/go2rtc (v1.8.2)
|
||||
# Uses https://github.com/AlexxIT/go2rtc (v1.8.3)
|
||||
go2rtc:
|
||||
|
||||
# Optional: jsmpeg stream configuration for WebUI
|
||||
|
||||
@@ -116,4 +116,4 @@ services:
|
||||
|
||||
:::
|
||||
|
||||
See [go2rtc WebRTC docs](https://github.com/AlexxIT/go2rtc/tree/v1.8.2#module-webrtc) for more information about this.
|
||||
See [go2rtc WebRTC docs](https://github.com/AlexxIT/go2rtc/tree/v1.8.3#module-webrtc) for more information about this.
|
||||
|
||||
@@ -295,28 +295,38 @@ To verify that the integration is working correctly, start Frigate and observe t
|
||||
## Rockchip RKNN-Toolkit-Lite2
|
||||
|
||||
This detector is only available if one of the following Rockchip SoCs is used:
|
||||
- RK3566/RK3568
|
||||
- RK3588/RK3588S
|
||||
- RV1103/RV1106
|
||||
- RK3568
|
||||
- RK3566
|
||||
- RK3562
|
||||
|
||||
These SoCs come with a NPU that will highly speed up detection.
|
||||
|
||||
### Setup
|
||||
|
||||
RKNN support is provided using the `-rk` suffix for the docker image. Moreover, privileged mode must be enabled by adding the `--privileged` flag to your docker run command or `privileged: true` to your `docker-compose.yml` file.
|
||||
Use a frigate docker image with `-rk` suffix and enable privileged mode by adding the `--privileged` flag to your docker run command or `privileged: true` to your `docker-compose.yml` file.
|
||||
|
||||
### Configuration
|
||||
|
||||
This `config.yml` shows all relevant options to configure the detector and explains them. All values shown are the default values (except for one). Lines that are required at least to use the detector are labeled as required, all other lines are optional.
|
||||
|
||||
```yaml
|
||||
detectors: # required
|
||||
rknn: # required
|
||||
type: rknn # required
|
||||
# core mask for npu
|
||||
core_mask: 0
|
||||
|
||||
model: # required
|
||||
# path to .rknn model file
|
||||
path: /models/yolov8n-320x320.rknn
|
||||
# name of yolov8 model or path to your own .rknn model file
|
||||
# possible values are:
|
||||
# - default-yolov8n
|
||||
# - default-yolov8s
|
||||
# - default-yolov8m
|
||||
# - default-yolov8l
|
||||
# - default-yolov8x
|
||||
# - /config/model_cache/rknn/your_custom_model.rknn
|
||||
path: default-yolov8n
|
||||
# width and height of detection frames
|
||||
width: 320
|
||||
height: 320
|
||||
@@ -325,4 +335,57 @@ model: # required
|
||||
input_pixel_format: bgr # required
|
||||
# shape of detection frame
|
||||
input_tensor: nhwc
|
||||
```
|
||||
```
|
||||
|
||||
Explanation for rknn specific options:
|
||||
- **core mask** controls which cores of your NPU should be used. This option applies only to SoCs with a multicore NPU (at the time of writing this in only the RK3588/S). The easiest way is to pass the value as a binary number. To do so, use the prefix `0b` and write a `0` to disable a core and a `1` to enable a core, whereas the last digit coresponds to core0, the second last to core1, etc. You also have to use the cores in ascending order (so you can't use core0 and core2; but you can use core0 and core1). Enabling more cores can reduce the inference speed, especially when using bigger models (see section below). Examples:
|
||||
- `core_mask: 0b000` or just `core_mask: 0` let the NPU decide which cores should be used. Default and recommended value.
|
||||
- `core_mask: 0b001` use only core0.
|
||||
- `core_mask: 0b011` use core0 and core1.
|
||||
- `core_mask: 0b110` use core1 and core2. **This does not** work, since core0 is disabled.
|
||||
|
||||
### Choosing a model
|
||||
|
||||
There are 5 default yolov8 models that differ in size and therefore load the NPU more or less. In ascending order, with the top one being the smallest and least computationally intensive model:
|
||||
|
||||
| Model | Size in mb |
|
||||
| ------- | ---------- |
|
||||
| yolov8n | 9 |
|
||||
| yolov8s | 25 |
|
||||
| yolov8m | 54 |
|
||||
| yolov8l | 90 |
|
||||
| yolov8x | 136 |
|
||||
|
||||
:::tip
|
||||
|
||||
You can get the load of your NPU with the following command:
|
||||
|
||||
```bash
|
||||
$ cat /sys/kernel/debug/rknpu/load
|
||||
>> NPU load: Core0: 0%, Core1: 0%, Core2: 0%,
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
- By default the rknn detector uses the yolov8n model (`model: path: default-yolov8n`). This model comes with the image, so no further steps than those mentioned above are necessary.
|
||||
- If you want to use a more precise model, you can pass `default-yolov8s`, `default-yolov8m`, `default-yolov8l` or `default-yolov8x` as `model: path:` option.
|
||||
- If the model does not exist, it will be automatically downloaded to `/config/model_cache/rknn`.
|
||||
- If your server has no internet connection, you can download the model from [this Github repository](https://github.com/MarcA711/rknn-models/releases) using another device and place it in the `config/model_cache/rknn` on your system.
|
||||
- Finally, you can also provide your own model. Note that only yolov8 models are currently supported. Moreover, you will need to convert your model to the rknn format using `rknn-toolkit2` on a x86 machine. Afterwards, you can place your `.rknn` model file in the `config/model_cache/rknn` directory on your system. Then you need to pass the path to your model using the `path` option of your `model` block like this:
|
||||
```yaml
|
||||
model:
|
||||
path: /config/model_cache/rknn/my-rknn-model.rknn
|
||||
```
|
||||
|
||||
:::tip
|
||||
|
||||
When you have a multicore NPU, you can enable all cores to reduce inference times. You should consider activating all cores if you use a larger model like yolov8l. If your NPU has 3 cores (like rk3588/S SoCs), you can enable all 3 cores using:
|
||||
|
||||
```yaml
|
||||
detectors:
|
||||
rknn:
|
||||
type: rknn
|
||||
core_mask: 0b111
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
@@ -87,11 +87,11 @@ The export page in the Frigate WebUI allows for exporting real time clips with a
|
||||
|
||||
## Syncing Recordings With Disk
|
||||
|
||||
In some cases the recordings files may be deleted but Frigate will not know this has happened. Sync on startup can be enabled which will tell Frigate to check the file system and delete any db entries for files which don't exist.
|
||||
In some cases the recordings files may be deleted but Frigate will not know this has happened. Recordings sync can be enabled which will tell Frigate to check the file system and delete any db entries for files which don't exist.
|
||||
|
||||
```yaml
|
||||
record:
|
||||
sync_on_startup: True
|
||||
sync_recordings: True
|
||||
```
|
||||
|
||||
:::warning
|
||||
|
||||
@@ -7,7 +7,7 @@ title: Restream
|
||||
|
||||
Frigate can restream your video feed as an RTSP feed for other applications such as Home Assistant to utilize it at `rtsp://<frigate_host>:8554/<camera_name>`. Port 8554 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](#reduce-connections-to-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.
|
||||
|
||||
Frigate uses [go2rtc](https://github.com/AlexxIT/go2rtc/tree/v1.8.2) to provide its restream and MSE/WebRTC capabilities. The go2rtc config is hosted at the `go2rtc` in the config, see [go2rtc docs](https://github.com/AlexxIT/go2rtc/tree/v1.8.2#configuration) for more advanced configurations and features.
|
||||
Frigate uses [go2rtc](https://github.com/AlexxIT/go2rtc/tree/v1.8.4) to provide its restream and MSE/WebRTC capabilities. The go2rtc config is hosted at the `go2rtc` in the config, see [go2rtc docs](https://github.com/AlexxIT/go2rtc/tree/v1.8.4#configuration) for more advanced configurations and features.
|
||||
|
||||
:::note
|
||||
|
||||
@@ -138,7 +138,7 @@ cameras:
|
||||
|
||||
## Advanced Restream Configurations
|
||||
|
||||
The [exec](https://github.com/AlexxIT/go2rtc/tree/v1.8.2#source-exec) source in go2rtc can be used for custom ffmpeg commands. An example is below:
|
||||
The [exec](https://github.com/AlexxIT/go2rtc/tree/v1.8.4#source-exec) source in go2rtc can be used for custom ffmpeg commands. An example is below:
|
||||
|
||||
NOTE: The output will need to be passed with two curly braces `{{output}}`
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ The following commands are used inside the container to ensure hardware accelera
|
||||
|
||||
**Raspberry Pi (64bit)**
|
||||
|
||||
This should show <50% CPU in top, and ~80% CPU without `-c:v h264_v4l2m2m`.
|
||||
This should show less than 50% CPU in top, and ~80% CPU without `-c:v h264_v4l2m2m`.
|
||||
|
||||
```shell
|
||||
ffmpeg -c:v h264_v4l2m2m -re -stream_loop -1 -i https://streams.videolan.org/ffmpeg/incoming/720p60.mp4 -f rawvideo -pix_fmt yuv420p pipe: > /dev/null
|
||||
@@ -131,7 +131,7 @@ ffmpeg -c:v h264_qsv -re -stream_loop -1 -i https://streams.videolan.org/ffmpeg/
|
||||
|
||||
- [Frigate source code](#frigate-core-web-and-docs)
|
||||
- All [core](#core) prerequisites _or_ another running Frigate instance locally available
|
||||
- Node.js 16
|
||||
- Node.js 20
|
||||
|
||||
### Making changes
|
||||
|
||||
@@ -183,7 +183,7 @@ npm run test
|
||||
### Prerequisites
|
||||
|
||||
- [Frigate source code](#frigate-core-web-and-docs)
|
||||
- Node.js 16
|
||||
- Node.js 20
|
||||
|
||||
### Making changes
|
||||
|
||||
@@ -201,7 +201,7 @@ npm run start
|
||||
|
||||
This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server.
|
||||
|
||||
The docs are built using [Docusaurus v2](https://v2.docusaurus.io). Please refer to the Docusaurus docs for more information on how to modify Frigate's documentation.
|
||||
The docs are built using [Docusaurus v3](https://docusaurus.io). Please refer to the Docusaurus docs for more information on how to modify Frigate's documentation.
|
||||
|
||||
#### 3. Build (optional)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ 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.md#reolink-410520-possibly-others). Wifi cameras are also not recommended. Their streams are less reliable and cause connection loss and/or lost video data.
|
||||
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.md#reolink-cameras). 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:
|
||||
|
||||
@@ -103,7 +103,7 @@ Frigate supports SBCs with the following Rockchip SoCs:
|
||||
- RV1103/RV1106
|
||||
- RK3562
|
||||
|
||||
Using the yolov8n model and an Orange Pi 5 Plus with RK3588 SoC inference speeds vary between 25-40 ms.
|
||||
Using the yolov8n model and an Orange Pi 5 Plus with RK3588 SoC inference speeds vary between 20 - 25 ms.
|
||||
|
||||
## What does Frigate use the CPU for and what does it use a detector for? (ELI5 Version)
|
||||
|
||||
|
||||
@@ -72,7 +72,6 @@ $ python -c 'print("{:.2f}MB".format(((1280 * 720 * 1.5 * 9 + 270480) / 1048576)
|
||||
|
||||
The shm size cannot be set per container for Home Assistant add-ons. However, this is probably not required since by default Home Assistant Supervisor allocates `/dev/shm` with half the size of your total memory. If your machine has 8GB of memory, chances are that Frigate will have access to up to 4GB without any additional configuration.
|
||||
|
||||
|
||||
### Raspberry Pi 3/4
|
||||
|
||||
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).
|
||||
@@ -81,23 +80,7 @@ Additionally, the USB Coral draws a considerable amount of power. If using any o
|
||||
|
||||
## Docker
|
||||
|
||||
Running in Docker with compose is the recommended install method:
|
||||
|
||||
:::note
|
||||
|
||||
The following officially supported builds are available:
|
||||
|
||||
`ghcr.io/blakeblackshear/frigate:stable` - Standard Frigate build for amd64 & RPi Optimized Frigate build for arm64
|
||||
`ghcr.io/blakeblackshear/frigate:stable-standard-arm64` - Standard Frigate build for arm64
|
||||
`ghcr.io/blakeblackshear/frigate:stable-tensorrt` - Frigate build specific for amd64 devices running an nvidia GPU
|
||||
|
||||
The following community supported builds are available:
|
||||
|
||||
`ghcr.io/blakeblackshear/frigate:stable-tensorrt-jp5` - Frigate build optimized for nvidia Jetson devices running Jetpack 5
|
||||
`ghcr.io/blakeblackshear/frigate:stable-tensorrt-jp4` - Frigate build optimized for nvidia Jetson devices running Jetpack 4.6
|
||||
`ghcr.io/blakeblackshear/frigate:stable-rk` - Frigate build for SBCs with Rockchip SoC
|
||||
|
||||
:::
|
||||
Running in Docker with compose is the recommended install method.
|
||||
|
||||
```yaml
|
||||
version: "3.9"
|
||||
@@ -150,6 +133,18 @@ docker run -d \
|
||||
ghcr.io/blakeblackshear/frigate:stable
|
||||
```
|
||||
|
||||
The official docker image tags for the current stable version are:
|
||||
|
||||
- `stable` - Standard Frigate build for amd64 & RPi Optimized Frigate build for arm64
|
||||
- `stable-standard-arm64` - Standard Frigate build for arm64
|
||||
- `stable-tensorrt` - Frigate build specific for amd64 devices running an nvidia GPU
|
||||
|
||||
The community supported docker image tags for the current stable version are:
|
||||
|
||||
- `stable-tensorrt-jp5` - Frigate build optimized for nvidia Jetson devices running Jetpack 5
|
||||
- `stable-tensorrt-jp4` - Frigate build optimized for nvidia Jetson devices running Jetpack 4.6
|
||||
- `stable-rk` - Frigate build for SBCs with Rockchip SoC
|
||||
|
||||
## Home Assistant Addon
|
||||
|
||||
:::caution
|
||||
@@ -157,6 +152,7 @@ docker run -d \
|
||||
As of HomeAssistant OS 10.2 and Core 2023.6 defining separate network storage for media is supported.
|
||||
|
||||
There are important limitations in Home Assistant Operating System to be aware of:
|
||||
|
||||
- Separate local storage for media is not yet supported by Home Assistant
|
||||
- AMD GPUs are not supported because HA OS does not include the mesa driver.
|
||||
- Nvidia GPUs are not supported because addons do not support the nvidia runtime.
|
||||
@@ -211,7 +207,6 @@ If you're running Frigate on a rack mounted server and want to passthough the Go
|
||||
|
||||
These settings were tested on DSM 7.1.1-42962 Update 4
|
||||
|
||||
|
||||
**General:**
|
||||
|
||||
The `Execute container using high privilege` option needs to be enabled in order to give the frigate container the elevated privileges it may need.
|
||||
@@ -220,14 +215,12 @@ The `Enable auto-restart` option can be enabled if you want the container to aut
|
||||
|
||||

|
||||
|
||||
|
||||
**Advanced Settings:**
|
||||
|
||||
If you want to use the password template feature, you should add the "FRIGATE_RTSP_PASSWORD" environment variable and set it to your preferred password under advanced settings. The rest of the environment variables should be left as default for now.
|
||||
|
||||

|
||||
|
||||
|
||||
**Port Settings:**
|
||||
|
||||
The network mode should be set to `bridge`. You need to map the default frigate container ports to your local Synology NAS ports that you want to use to access Frigate.
|
||||
@@ -236,7 +229,6 @@ There may be other services running on your NAS that are using the same ports th
|
||||
|
||||

|
||||
|
||||
|
||||
**Volume Settings:**
|
||||
|
||||
You need to configure 2 paths:
|
||||
@@ -250,14 +242,15 @@ You need to configure 2 paths:
|
||||
|
||||
These instructions were tested on a QNAP with an Intel J3455 CPU and 16G RAM, running QTS 4.5.4.2117.
|
||||
|
||||
QNAP has a graphic tool named Container Station to install and manage docker containers. However, there are two limitations with Container Station that make it unsuitable to install Frigate:
|
||||
QNAP has a graphic tool named Container Station to install and manage docker containers. However, there are two limitations with Container Station that make it unsuitable to install Frigate:
|
||||
|
||||
1. Container Station does not incorporate GitHub Container Registry (ghcr), which hosts Frigate docker image version 0.12.0 and above.
|
||||
2. Container Station uses default 64 Mb shared memory size (shm-size), and does not have a mechanism to adjust it. Frigate requires a larger shm-size to be able to work properly with more than two high resolution cameras.
|
||||
2. Container Station uses default 64 Mb shared memory size (shm-size), and does not have a mechanism to adjust it. Frigate requires a larger shm-size to be able to work properly with more than two high resolution cameras.
|
||||
|
||||
Because of above limitations, the installation has to be done from command line. Here are the steps:
|
||||
Because of above limitations, the installation has to be done from command line. Here are the steps:
|
||||
|
||||
**Preparation**
|
||||
|
||||
1. Install Container Station from QNAP App Center if it is not installed.
|
||||
2. Enable ssh on your QNAP (please do an Internet search on how to do this).
|
||||
3. Prepare Frigate config file, name it `config.yml`.
|
||||
@@ -268,7 +261,8 @@ Because of above limitations, the installation has to be done from command line.
|
||||
**Installation**
|
||||
|
||||
Run the following commands to install Frigate (using `stable` version as example):
|
||||
```bash
|
||||
|
||||
```shell
|
||||
# Download Frigate image
|
||||
docker pull ghcr.io/blakeblackshear/frigate:stable
|
||||
# Create directory to host Frigate config file on QNAP file system.
|
||||
@@ -309,6 +303,4 @@ docker run \
|
||||
ghcr.io/blakeblackshear/frigate:stable
|
||||
```
|
||||
|
||||
Log into QNAP, open Container Station. Frigate docker container should be listed under 'Overview' and running. Visit Frigate Web UI by clicking Frigate docker, and then clicking the URL shown at the top of the detail page.
|
||||
|
||||
|
||||
Log into QNAP, open Container Station. Frigate docker container should be listed under 'Overview' and running. Visit Frigate Web UI by clicking Frigate docker, and then clicking the URL shown at the top of the detail page.
|
||||
|
||||
67
docs/docs/frigate/video_pipeline.md
Normal file
67
docs/docs/frigate/video_pipeline.md
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
id: video_pipeline
|
||||
title: Video pipeline
|
||||
---
|
||||
|
||||
Frigate uses a sophisticated video pipeline that starts with the camera feed and progressively applies transformations to it (e.g. decoding, motion detection, etc.).
|
||||
|
||||
This guide provides an overview to help users understand some of the key Frigate concepts.
|
||||
|
||||
## Overview
|
||||
|
||||
At a high level, there are five processing steps that could be applied to a camera feed
|
||||
|
||||
```mermaid
|
||||
%%{init: {"themeVariables": {"edgeLabelBackground": "transparent"}}}%%
|
||||
|
||||
flowchart LR
|
||||
Feed(Feed\nacquisition) --> Decode(Video\ndecoding)
|
||||
Decode --> Motion(Motion\ndetection)
|
||||
Motion --> Object(Object\ndetection)
|
||||
Feed --> Recording(Recording\nand\nvisualization)
|
||||
Motion --> Recording
|
||||
Object --> Recording
|
||||
```
|
||||
|
||||
As the diagram shows, all feeds first need to be acquired. Depending on the data source, it may be as simple as using FFmpeg to connect to an RTSP source via TCP or something more involved like connecting to an Apple Homekit camera using go2rtc. A single camera can produce a main (i.e. high resolution) and a sub (i.e. lower resolution) video feed.
|
||||
|
||||
Typically, the sub-feed will be decoded to produce full-frame images. As part of this process, the resolution may be downscaled and an image sampling frequency may be imposed (e.g. keep 5 frames per second).
|
||||
|
||||
These frames will then be compared over time to detect movement areas (a.k.a. motion boxes). These motion boxes are combined into motion regions and are analyzed by a machine learning model to detect known objects. Finally, the snapshot and recording retention config will decide what video clips and events should be saved.
|
||||
|
||||
## Detailed view of the video pipeline
|
||||
|
||||
The following diagram adds a lot more detail than the simple view explained before. The goal is to show the detailed data paths between the processing steps.
|
||||
|
||||
```mermaid
|
||||
%%{init: {"themeVariables": {"edgeLabelBackground": "transparent"}}}%%
|
||||
|
||||
flowchart TD
|
||||
RecStore[(Recording\nstore)]
|
||||
SnapStore[(Snapshot\nstore)]
|
||||
|
||||
subgraph Acquisition
|
||||
Cam["Camera"] -->|FFmpeg supported| Stream
|
||||
Cam -->|"Other streaming\nprotocols"| go2rtc
|
||||
go2rtc("go2rtc") --> Stream
|
||||
Stream[Capture main and\nsub streams] --> |detect stream|Decode(Decode and\ndownscale)
|
||||
end
|
||||
subgraph Motion
|
||||
Decode --> MotionM(Apply\nmotion masks)
|
||||
MotionM --> MotionD(Motion\ndetection)
|
||||
end
|
||||
subgraph Detection
|
||||
MotionD --> |motion regions| ObjectD(Object detection)
|
||||
Decode --> ObjectD
|
||||
ObjectD --> ObjectFilter(Apply object filters & zones)
|
||||
ObjectFilter --> ObjectZ(Track objects)
|
||||
end
|
||||
Decode --> |decoded frames|Birdseye
|
||||
MotionD --> |motion event|Birdseye
|
||||
ObjectZ --> |object event|Birdseye
|
||||
|
||||
MotionD --> |"video segments\n(retain motion)"|RecStore
|
||||
ObjectZ --> |detection clip|RecStore
|
||||
Stream -->|"video segments\n(retain all)"| RecStore
|
||||
ObjectZ --> |detection snapshot|SnapStore
|
||||
```
|
||||
@@ -3,6 +3,8 @@ id: configuring_go2rtc
|
||||
title: Configuring go2rtc
|
||||
---
|
||||
|
||||
# Configuring go2rtc
|
||||
|
||||
Use of the bundled go2rtc is optional. You can still configure FFmpeg to connect directly to your cameras. However, adding go2rtc to your configuration is required for the following features:
|
||||
|
||||
- WebRTC or MSE for live viewing with higher resolutions and frame rates than the jsmpeg stream which is limited to the detect stream
|
||||
@@ -11,7 +13,7 @@ Use of the bundled go2rtc is optional. You can still configure FFmpeg to connect
|
||||
|
||||
# Setup a go2rtc stream
|
||||
|
||||
First, you will want to configure go2rtc to connect to your camera stream by adding the stream you want to use for live view in your Frigate config file. If you set the stream name under go2rtc to match the name of your camera, it will automatically be mapped and you will get additional live view options for the camera. Avoid changing any other parts of your config at this step. Note that go2rtc supports [many different stream types](https://github.com/AlexxIT/go2rtc/tree/v1.8.2#module-streams), not just rtsp.
|
||||
First, you will want to configure go2rtc to connect to your camera stream by adding the stream you want to use for live view in your Frigate config file. If you set the stream name under go2rtc to match the name of your camera, it will automatically be mapped and you will get additional live view options for the camera. Avoid changing any other parts of your config at this step. Note that go2rtc supports [many different stream types](https://github.com/AlexxIT/go2rtc/tree/v1.8.4#module-streams), not just rtsp.
|
||||
|
||||
```yaml
|
||||
go2rtc:
|
||||
@@ -24,7 +26,7 @@ The easiest live view to get working is MSE. After adding this to the config, re
|
||||
|
||||
### What if my video doesn't play?
|
||||
|
||||
If you are unable to see your video feed, first check the go2rtc logs in the Frigate UI under Logs in the sidebar. If go2rtc is having difficulty connecting to your camera, you should see some error messages in the log. If you do not see any errors, then the video codec of the stream may not be supported in your browser. If your camera stream is set to H265, try switching to H264. You can see more information about [video codec compatibility](https://github.com/AlexxIT/go2rtc/tree/v1.8.2#codecs-madness) in the go2rtc documentation. If you are not able to switch your camera settings from H265 to H264 or your stream is a different format such as MJPEG, you can use go2rtc to re-encode the video using the [FFmpeg parameters](https://github.com/AlexxIT/go2rtc/tree/v1.8.2#source-ffmpeg). It supports rotating and resizing video feeds and hardware acceleration. Keep in mind that transcoding video from one format to another is a resource intensive task and you may be better off using the built-in jsmpeg view. Here is an example of a config that will re-encode the stream to H264 without hardware acceleration:
|
||||
If you are unable to see your video feed, first check the go2rtc logs in the Frigate UI under Logs in the sidebar. If go2rtc is having difficulty connecting to your camera, you should see some error messages in the log. If you do not see any errors, then the video codec of the stream may not be supported in your browser. If your camera stream is set to H265, try switching to H264. You can see more information about [video codec compatibility](https://github.com/AlexxIT/go2rtc/tree/v1.8.4#codecs-madness) in the go2rtc documentation. If you are not able to switch your camera settings from H265 to H264 or your stream is a different format such as MJPEG, you can use go2rtc to re-encode the video using the [FFmpeg parameters](https://github.com/AlexxIT/go2rtc/tree/v1.8.4#source-ffmpeg). It supports rotating and resizing video feeds and hardware acceleration. Keep in mind that transcoding video from one format to another is a resource intensive task and you may be better off using the built-in jsmpeg view. Here is an example of a config that will re-encode the stream to H264 without hardware acceleration:
|
||||
|
||||
```yaml
|
||||
go2rtc:
|
||||
|
||||
@@ -3,7 +3,145 @@ id: getting_started
|
||||
title: Getting started
|
||||
---
|
||||
|
||||
This guide walks through the steps to build a configuration file for Frigate. It assumes that you already have an environment setup as described in [Installation](../frigate/installation.md). You should also configure your cameras according to the [camera setup guide](/frigate/camera_setup). Pay particular attention to the section on choosing a detect resolution.
|
||||
# Getting Started
|
||||
|
||||
## Setting up hardware
|
||||
|
||||
This section guides you through setting up a server with Debian Bookworm and Docker. If you already have an environment with Linux and Docker installed, you can continue to [Installing Frigate](#installing-frigate) below.
|
||||
|
||||
### Install Debian 12 (Bookworm)
|
||||
|
||||
There are many guides on how to install Debian Server, so this will be an abbreviated guide. Connect a temporary monitor and keyboard to your device so you can install a minimal server without a desktop environment.
|
||||
|
||||
#### Prepare installation media
|
||||
|
||||
1. Download the small installation image from the [Debian website](https://www.debian.org/distrib/netinst)
|
||||
1. Flash the ISO to a USB device (popular tool is [balena Etcher](https://etcher.balena.io/))
|
||||
1. Boot your device from USB
|
||||
|
||||
#### Install and setup Debian for remote access
|
||||
|
||||
1. Ensure your device is connected to the network so updates and software options can be installed
|
||||
1. Choose the non-graphical install option if you don't have a mouse connected, but either install method works fine
|
||||
1. You will be prompted to set the root user password and create a user with a password
|
||||
1. Install the minimum software. Fewer dependencies result in less maintenance.
|
||||
1. Uncheck "Debian desktop environment" and "GNOME"
|
||||
1. Check "SSH server"
|
||||
1. Keep "standard system utilities" checked
|
||||
1. After reboot, login as root at the command prompt to add user to sudoers
|
||||
1. Install sudo
|
||||
```bash
|
||||
apt update && apt install -y sudo
|
||||
```
|
||||
1. Add the user you created to the sudo group (change `blake` to your own user)
|
||||
```bash
|
||||
usermod -aG sudo blake
|
||||
```
|
||||
1. Shutdown by running `poweroff`
|
||||
|
||||
At this point, you can install the device in a permanent location. The remaining steps can be performed via SSH from another device. If you don't have an SSH client, you can install one of the options listed in the [Visual Studio Code documentation](https://code.visualstudio.com/docs/remote/troubleshooting#_installing-a-supported-ssh-client).
|
||||
|
||||
#### Finish setup via SSH
|
||||
|
||||
1. Connect via SSH and login with your non-root user created during install
|
||||
1. Setup passwordless sudo so you don't have to type your password for each sudo command (change `blake` in the command below to your user)
|
||||
|
||||
```bash
|
||||
echo 'blake ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/user
|
||||
```
|
||||
|
||||
1. Logout and login again to activate passwordless sudo
|
||||
1. Setup automatic security updates for the OS (optional)
|
||||
1. Ensure everything is up to date by running
|
||||
```bash
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
```
|
||||
1. Install unattended upgrades
|
||||
```bash
|
||||
sudo apt install -y unattended-upgrades
|
||||
echo unattended-upgrades unattended-upgrades/enable_auto_updates boolean true | sudo debconf-set-selections
|
||||
sudo dpkg-reconfigure -f noninteractive unattended-upgrades
|
||||
```
|
||||
|
||||
Now you have a minimal Debian server that requires very little maintenance.
|
||||
|
||||
### Install Docker
|
||||
|
||||
1. Install Docker Engine (not Docker Desktop) using the [official docs](https://docs.docker.com/engine/install/debian/)
|
||||
1. Specifically, follow the steps in the [Install using the apt repository](https://docs.docker.com/engine/install/debian/#install-using-the-repository) section
|
||||
2. Add your user to the docker group as described in the [Linux postinstall steps](https://docs.docker.com/engine/install/linux-postinstall/)
|
||||
|
||||
## Installing Frigate
|
||||
|
||||
This section shows how to create a minimal directory structure for a Docker installation on Debian. If you have installed Frigate as a Home Assistant addon or another way, you can continue to [Configuring Frigate](#configuring-frigate).
|
||||
|
||||
### Setup directories
|
||||
|
||||
Frigate requires a valid config file to start. The following directory structure is the bare minimum to get started. Once Frigate is running, you can use the built-in config editor which supports config validation.
|
||||
|
||||
```
|
||||
.
|
||||
├── docker-compose.yml
|
||||
├── config/
|
||||
│ └── config.yml
|
||||
└── storage/
|
||||
```
|
||||
|
||||
This will create the above structure:
|
||||
|
||||
```bash
|
||||
mkdir storage config && touch docker-compose.yml config/config.yml
|
||||
```
|
||||
|
||||
If you are setting up Frigate on a Linux device via SSH, you can use [nano](https://itsfoss.com/nano-editor-guide/) to edit the following files. If you prefer to edit remote files with a full editor instead of a terminal, I recommend using [Visual Studio Code](https://code.visualstudio.com/) with the [Remote SSH extension](https://code.visualstudio.com/docs/remote/ssh-tutorial).
|
||||
|
||||
:::note
|
||||
|
||||
This `docker-compose.yml` file is just a starter for amd64 devices. You will need to customize it for your setup as detailed in the [Installation docs](/frigate/installation#docker).
|
||||
|
||||
:::
|
||||
`docker-compose.yml`
|
||||
|
||||
```yaml
|
||||
version: "3.9"
|
||||
services:
|
||||
frigate:
|
||||
container_name: frigate
|
||||
restart: unless-stopped
|
||||
image: ghcr.io/blakeblackshear/frigate:stable
|
||||
volumes:
|
||||
- ./config:/config
|
||||
- ./storage:/media/frigate
|
||||
- type: tmpfs # Optional: 1GB of memory, reduces SSD/SD Card wear
|
||||
target: /tmp/cache
|
||||
tmpfs:
|
||||
size: 1000000000
|
||||
ports:
|
||||
- "5000:5000"
|
||||
- "8554:8554" # RTSP feeds
|
||||
```
|
||||
|
||||
`config.yml`
|
||||
|
||||
```yaml
|
||||
mqtt:
|
||||
enabled: False
|
||||
|
||||
cameras:
|
||||
dummy_camera: # <--- this will be changed to your actual camera later
|
||||
enabled: False
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://127.0.0.1:554/rtsp
|
||||
roles:
|
||||
- detect
|
||||
```
|
||||
|
||||
Now you should be able to start Frigate by running `docker compose up -d` from within the folder containing `docker-compose.yml`. Frigate should now be accessible at `server_ip:5000` and you can finish the configuration using the built-in configuration editor.
|
||||
|
||||
## Configuring Frigate
|
||||
|
||||
This section assumes that you already have an environment setup as described in [Installation](../frigate/installation.md). You should also configure your cameras according to the [camera setup guide](/frigate/camera_setup). Pay particular attention to the section on choosing a detect resolution.
|
||||
|
||||
### Step 1: Add a detect stream
|
||||
|
||||
@@ -15,6 +153,7 @@ mqtt:
|
||||
|
||||
cameras:
|
||||
name_of_your_camera: # <------ Name the camera
|
||||
enabled: True
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://10.0.10.10:554/rtsp # <----- The stream you want to use for detection
|
||||
@@ -36,7 +175,21 @@ FFmpeg arguments for other types of cameras can be found [here](../configuration
|
||||
|
||||
Now that you have a working camera configuration, you want to setup hardware acceleration to minimize the CPU required to decode your video streams. See the [hardware acceleration](../configuration/hardware_acceleration.md) config reference for examples applicable to your hardware.
|
||||
|
||||
Here is an example configuration with hardware acceleration configured for Intel processors with an integrated GPU using the [preset](../configuration/ffmpeg_presets.md):
|
||||
Here is an example configuration with hardware acceleration configured to work with most Intel processors with an integrated GPU using the [preset](../configuration/ffmpeg_presets.md):
|
||||
|
||||
`docker-compose.yml` (after modifying, you will need to run `docker compose up -d` to apply changes)
|
||||
|
||||
```yaml
|
||||
version: "3.9"
|
||||
services:
|
||||
frigate:
|
||||
...
|
||||
devices:
|
||||
- /dev/dri/renderD128 # for intel hwaccel, needs to be updated for your hardware
|
||||
...
|
||||
```
|
||||
|
||||
`config.yml`
|
||||
|
||||
```yaml
|
||||
mqtt: ...
|
||||
@@ -53,6 +206,19 @@ cameras:
|
||||
|
||||
By default, Frigate will use a single CPU detector. If you have a USB Coral, you will need to add a detectors section to your config.
|
||||
|
||||
`docker-compose.yml` (after modifying, you will need to run `docker compose up -d` to apply changes)
|
||||
|
||||
```yaml
|
||||
version: "3.9"
|
||||
services:
|
||||
frigate:
|
||||
...
|
||||
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
|
||||
...
|
||||
```
|
||||
|
||||
```yaml
|
||||
mqtt: ...
|
||||
|
||||
|
||||
@@ -267,6 +267,11 @@ Returns the snapshot image from the specific point in that cameras recordings.
|
||||
|
||||
Returns the latest camera image with the regions grid overlaid.
|
||||
|
||||
| param | Type | Description |
|
||||
| ------------ | ----- | ------------------------------------------------------------------------------------------ |
|
||||
| `color` | str | The color of the grid (red,green,blue,black,white). Defaults to "green". |
|
||||
| `font_scale` | float | Font scale. Can be used to increase font size on high resolution cameras. Defaults to 0.5. |
|
||||
|
||||
### `GET /clips/<camera>-<id>.jpg`
|
||||
|
||||
JPG snapshot for the given camera and event id.
|
||||
@@ -297,6 +302,14 @@ It is also possible to export this recording as a timelapse.
|
||||
}
|
||||
```
|
||||
|
||||
### `DELETE /api/export/<export_name>`
|
||||
|
||||
Delete an export from disk.
|
||||
|
||||
### `PATCH /api/export/<export_name_current>/<export_name_new>`
|
||||
|
||||
Renames an export.
|
||||
|
||||
### `GET /api/<camera_name>/recordings/summary`
|
||||
|
||||
Hourly summary of recordings data for a camera.
|
||||
|
||||
@@ -177,7 +177,7 @@ The Frigate integration seamlessly supports the use of multiple Frigate servers.
|
||||
In order for multiple Frigate instances to function correctly, the
|
||||
`topic_prefix` and `client_id` parameters must be set differently per server.
|
||||
See [MQTT
|
||||
configuration](mqtt.md)
|
||||
configuration](mqtt)
|
||||
for how to set these.
|
||||
|
||||
#### API URLs
|
||||
|
||||
@@ -3,7 +3,7 @@ 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...`
|
||||
### 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.
|
||||
|
||||
@@ -21,18 +21,18 @@ 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
|
||||
#### 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
|
||||
##### 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
|
||||
##### 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
|
||||
#### 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.
|
||||
|
||||
@@ -1,70 +1,77 @@
|
||||
const path = require('path');
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
title: 'Frigate',
|
||||
tagline: 'NVR With Realtime Object Detection for IP Cameras',
|
||||
url: 'https://docs.frigate.video',
|
||||
baseUrl: '/',
|
||||
onBrokenLinks: 'throw',
|
||||
onBrokenMarkdownLinks: 'warn',
|
||||
favicon: 'img/favicon.ico',
|
||||
organizationName: 'blakeblackshear',
|
||||
projectName: 'frigate',
|
||||
title: "Frigate",
|
||||
tagline: "NVR With Realtime Object Detection for IP Cameras",
|
||||
url: "https://docs.frigate.video",
|
||||
baseUrl: "/",
|
||||
onBrokenLinks: "throw",
|
||||
onBrokenMarkdownLinks: "warn",
|
||||
favicon: "img/favicon.ico",
|
||||
organizationName: "blakeblackshear",
|
||||
projectName: "frigate",
|
||||
themes: ["@docusaurus/theme-mermaid"],
|
||||
markdown: {
|
||||
mermaid: true,
|
||||
},
|
||||
themeConfig: {
|
||||
algolia: {
|
||||
appId: 'WIURGBNBPY',
|
||||
apiKey: 'd02cc0a6a61178b25da550212925226b',
|
||||
indexName: 'frigate',
|
||||
appId: "WIURGBNBPY",
|
||||
apiKey: "d02cc0a6a61178b25da550212925226b",
|
||||
indexName: "frigate",
|
||||
},
|
||||
docs: {
|
||||
sidebar: {
|
||||
hideable: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
prism: {
|
||||
additionalLanguages: ["bash", "json"],
|
||||
},
|
||||
navbar: {
|
||||
title: 'Frigate',
|
||||
title: "Frigate",
|
||||
logo: {
|
||||
alt: 'Frigate',
|
||||
src: 'img/logo.svg',
|
||||
srcDark: 'img/logo-dark.svg',
|
||||
alt: "Frigate",
|
||||
src: "img/logo.svg",
|
||||
srcDark: "img/logo-dark.svg",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
to: '/',
|
||||
activeBasePath: 'docs',
|
||||
label: 'Docs',
|
||||
position: 'left',
|
||||
to: "/",
|
||||
activeBasePath: "docs",
|
||||
label: "Docs",
|
||||
position: "left",
|
||||
},
|
||||
{
|
||||
href: 'https://frigate.video',
|
||||
label: 'Website',
|
||||
position: 'right',
|
||||
href: "https://frigate.video",
|
||||
label: "Website",
|
||||
position: "right",
|
||||
},
|
||||
{
|
||||
href: 'http://demo.frigate.video',
|
||||
label: 'Demo',
|
||||
position: 'right',
|
||||
href: "http://demo.frigate.video",
|
||||
label: "Demo",
|
||||
position: "right",
|
||||
},
|
||||
{
|
||||
href: 'https://github.com/blakeblackshear/frigate',
|
||||
label: 'GitHub',
|
||||
position: 'right',
|
||||
href: "https://github.com/blakeblackshear/frigate",
|
||||
label: "GitHub",
|
||||
position: "right",
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
style: 'dark',
|
||||
style: "dark",
|
||||
links: [
|
||||
{
|
||||
title: 'Community',
|
||||
title: "Community",
|
||||
items: [
|
||||
{
|
||||
label: 'GitHub',
|
||||
href: 'https://github.com/blakeblackshear/frigate',
|
||||
label: "GitHub",
|
||||
href: "https://github.com/blakeblackshear/frigate",
|
||||
},
|
||||
{
|
||||
label: 'Discussions',
|
||||
href: 'https://github.com/blakeblackshear/frigate/discussions',
|
||||
label: "Discussions",
|
||||
href: "https://github.com/blakeblackshear/frigate/discussions",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -72,21 +79,22 @@ module.exports = {
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Blake Blackshear`,
|
||||
},
|
||||
},
|
||||
plugins: [path.resolve(__dirname, 'plugins', 'raw-loader')],
|
||||
plugins: [path.resolve(__dirname, "plugins", "raw-loader")],
|
||||
presets: [
|
||||
[
|
||||
'@docusaurus/preset-classic',
|
||||
"@docusaurus/preset-classic",
|
||||
{
|
||||
docs: {
|
||||
routeBasePath: '/',
|
||||
sidebarPath: require.resolve('./sidebars.js'),
|
||||
routeBasePath: "/",
|
||||
sidebarPath: require.resolve("./sidebars.js"),
|
||||
// Please change this to your repo.
|
||||
editUrl: 'https://github.com/blakeblackshear/frigate/edit/master/docs/',
|
||||
sidebarCollapsible: false
|
||||
editUrl:
|
||||
"https://github.com/blakeblackshear/frigate/edit/master/docs/",
|
||||
sidebarCollapsible: false,
|
||||
},
|
||||
|
||||
theme: {
|
||||
customCss: require.resolve('./src/css/custom.css'),
|
||||
customCss: require.resolve("./src/css/custom.css"),
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
19847
docs/package-lock.json
generated
19847
docs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -14,14 +14,15 @@
|
||||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^2.4.1",
|
||||
"@docusaurus/preset-classic": "^2.4.1",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"@docusaurus/core": "3.0.0",
|
||||
"@docusaurus/preset-classic": "3.0.0",
|
||||
"@docusaurus/theme-mermaid": "3.0.0",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^1.2.1",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"prism-react-renderer": "^2.1.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
@@ -36,10 +37,11 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^2.4.0",
|
||||
"@types/react": "^17.0.0"
|
||||
"@docusaurus/module-type-aliases": "^3.0.0",
|
||||
"@docusaurus/types": "^3.0.0",
|
||||
"@types/react": "^18.2.29"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.14"
|
||||
"node": ">=18.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ module.exports = {
|
||||
"frigate/hardware",
|
||||
"frigate/installation",
|
||||
"frigate/camera_setup",
|
||||
"frigate/video_pipeline",
|
||||
],
|
||||
Guides: [
|
||||
"guides/getting_started",
|
||||
@@ -21,7 +22,7 @@ module.exports = {
|
||||
{
|
||||
type: "link",
|
||||
label: "Go2RTC Configuration Reference",
|
||||
href: "https://github.com/AlexxIT/go2rtc/tree/v1.8.2#configuration",
|
||||
href: "https://github.com/AlexxIT/go2rtc/tree/v1.8.4#configuration",
|
||||
},
|
||||
],
|
||||
Detectors: [
|
||||
|
||||
@@ -185,6 +185,13 @@ class Dispatcher:
|
||||
ptz_autotracker_settings = self.config.cameras[camera_name].onvif.autotracking
|
||||
|
||||
if payload == "ON":
|
||||
if not self.config.cameras[
|
||||
camera_name
|
||||
].onvif.autotracking.enabled_in_config:
|
||||
logger.error(
|
||||
"Autotracking must be enabled in the config to be turned on via MQTT."
|
||||
)
|
||||
return
|
||||
if not self.ptz_metrics[camera_name]["ptz_autotracker_enabled"].value:
|
||||
logger.info(f"Turning on ptz autotracker for {camera_name}")
|
||||
self.ptz_metrics[camera_name]["ptz_autotracker_enabled"].value = True
|
||||
|
||||
@@ -71,7 +71,7 @@ class MqttClient(Communicator): # type: ignore[misc]
|
||||
)
|
||||
self.publish(
|
||||
f"{camera_name}/ptz_autotracker/state",
|
||||
"ON" if camera.onvif.autotracking.enabled else "OFF",
|
||||
"ON" if camera.onvif.autotracking.enabled_in_config else "OFF",
|
||||
retain=True,
|
||||
)
|
||||
self.publish(
|
||||
|
||||
@@ -19,6 +19,7 @@ from frigate.const import (
|
||||
CACHE_DIR,
|
||||
CACHE_SEGMENT_FORMAT,
|
||||
DEFAULT_DB_PATH,
|
||||
MAX_PRE_CAPTURE,
|
||||
REGEX_CAMERA_NAME,
|
||||
YAML_EXT,
|
||||
)
|
||||
@@ -184,6 +185,9 @@ class PtzAutotrackConfig(FrigateBaseModel):
|
||||
default=[],
|
||||
title="Internal value used for PTZ movements based on the speed of your camera's motor.",
|
||||
)
|
||||
enabled_in_config: Optional[bool] = Field(
|
||||
title="Keep track of original state of autotracking."
|
||||
)
|
||||
|
||||
@validator("movement_weights", pre=True)
|
||||
def validate_weights(cls, v):
|
||||
@@ -229,7 +233,9 @@ class RetainConfig(FrigateBaseModel):
|
||||
|
||||
|
||||
class EventsConfig(FrigateBaseModel):
|
||||
pre_capture: int = Field(default=5, title="Seconds to retain before event starts.")
|
||||
pre_capture: int = Field(
|
||||
default=5, title="Seconds to retain before event starts.", le=MAX_PRE_CAPTURE
|
||||
)
|
||||
post_capture: int = Field(default=5, title="Seconds to retain after event ends.")
|
||||
required_zones: List[str] = Field(
|
||||
default_factory=list,
|
||||
@@ -256,8 +262,8 @@ class RecordExportConfig(FrigateBaseModel):
|
||||
|
||||
class RecordConfig(FrigateBaseModel):
|
||||
enabled: bool = Field(default=False, title="Enable record on all cameras.")
|
||||
sync_on_startup: bool = Field(
|
||||
default=False, title="Sync recordings with disk on startup."
|
||||
sync_recordings: bool = Field(
|
||||
default=False, title="Sync recordings with disk on startup and once a day."
|
||||
)
|
||||
expire_interval: int = Field(
|
||||
default=60,
|
||||
@@ -1191,6 +1197,9 @@ class FrigateConfig(FrigateBaseModel):
|
||||
# set config pre-value
|
||||
camera_config.record.enabled_in_config = camera_config.record.enabled
|
||||
camera_config.audio.enabled_in_config = camera_config.audio.enabled
|
||||
camera_config.onvif.autotracking.enabled_in_config = (
|
||||
camera_config.onvif.autotracking.enabled
|
||||
)
|
||||
|
||||
# Add default filters
|
||||
object_keys = camera_config.objects.track
|
||||
|
||||
@@ -51,7 +51,9 @@ DRIVER_INTEL_iHD = "iHD"
|
||||
# Record Values
|
||||
|
||||
CACHE_SEGMENT_FORMAT = "%Y%m%d%H%M%S%z"
|
||||
MAX_PRE_CAPTURE = 60
|
||||
MAX_SEGMENT_DURATION = 600
|
||||
MAX_SEGMENTS_IN_CACHE = 6
|
||||
MAX_PLAYLIST_SECONDS = 7200 # support 2 hour segments for a single playlist to account for cameras with inconsistent segment times
|
||||
|
||||
# Internal Comms Topics
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import logging
|
||||
import os.path
|
||||
import urllib.request
|
||||
from typing import Literal
|
||||
|
||||
import cv2
|
||||
import cv2.dnn
|
||||
import numpy as np
|
||||
|
||||
try:
|
||||
@@ -22,35 +22,115 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
DETECTOR_KEY = "rknn"
|
||||
|
||||
supported_socs = ["rk3562", "rk3566", "rk3568", "rk3588"]
|
||||
|
||||
yolov8_suffix = {
|
||||
"default-yolov8n": "n",
|
||||
"default-yolov8s": "s",
|
||||
"default-yolov8m": "m",
|
||||
"default-yolov8l": "l",
|
||||
"default-yolov8x": "x",
|
||||
}
|
||||
|
||||
|
||||
class RknnDetectorConfig(BaseDetectorConfig):
|
||||
type: Literal[DETECTOR_KEY]
|
||||
score_thresh: float = Field(
|
||||
default=0.5, ge=0, le=1, title="Minimal confidence for detection."
|
||||
)
|
||||
nms_thresh: float = Field(
|
||||
default=0.45, ge=0, le=1, title="IoU threshold for non-maximum suppression."
|
||||
)
|
||||
core_mask: int = Field(default=0, ge=0, le=7, title="Core mask for NPU.")
|
||||
|
||||
|
||||
class Rknn(DetectionApi):
|
||||
type_key = DETECTOR_KEY
|
||||
|
||||
def __init__(self, config: RknnDetectorConfig):
|
||||
# find out SoC
|
||||
try:
|
||||
with open("/proc/device-tree/compatible") as file:
|
||||
soc = file.read().split(",")[-1].strip("\x00")
|
||||
except FileNotFoundError:
|
||||
logger.error("Make sure to run docker in privileged mode.")
|
||||
raise Exception("Make sure to run docker in privileged mode.")
|
||||
|
||||
if soc not in supported_socs:
|
||||
logger.error(
|
||||
"Your SoC is not supported. Your SoC is: {}. Currently these SoCs are supported: {}.".format(
|
||||
soc, supported_socs
|
||||
)
|
||||
)
|
||||
raise Exception(
|
||||
"Your SoC is not supported. Your SoC is: {}. Currently these SoCs are supported: {}.".format(
|
||||
soc, supported_socs
|
||||
)
|
||||
)
|
||||
|
||||
if not os.path.isfile("/usr/lib/librknnrt.so"):
|
||||
if "rk356" in soc:
|
||||
os.rename("/usr/lib/librknnrt_rk356x.so", "/usr/lib/librknnrt.so")
|
||||
elif "rk3588" in soc:
|
||||
os.rename("/usr/lib/librknnrt_rk3588.so", "/usr/lib/librknnrt.so")
|
||||
|
||||
self.model_path = config.model.path or "default-yolov8n"
|
||||
self.core_mask = config.core_mask
|
||||
self.height = config.model.height
|
||||
self.width = config.model.width
|
||||
self.score_thresh = config.score_thresh
|
||||
self.nms_thresh = config.nms_thresh
|
||||
|
||||
self.model_path = config.model.path or "/models/yolov8n-320x320.rknn"
|
||||
if self.model_path in yolov8_suffix:
|
||||
if self.model_path == "default-yolov8n":
|
||||
self.model_path = "/models/rknn/yolov8n-320x320-{soc}.rknn".format(
|
||||
soc=soc
|
||||
)
|
||||
else:
|
||||
model_suffix = yolov8_suffix[self.model_path]
|
||||
self.model_path = (
|
||||
"/config/model_cache/rknn/yolov8{suffix}-320x320-{soc}.rknn".format(
|
||||
suffix=model_suffix, soc=soc
|
||||
)
|
||||
)
|
||||
|
||||
os.makedirs("/config/model_cache/rknn", exist_ok=True)
|
||||
if not os.path.isfile(self.model_path):
|
||||
logger.info(
|
||||
"Downloading yolov8{suffix} model.".format(suffix=model_suffix)
|
||||
)
|
||||
urllib.request.urlretrieve(
|
||||
"https://github.com/MarcA711/rknn-models/releases/download/v1.5.2-{soc}/yolov8{suffix}-320x320-{soc}.rknn".format(
|
||||
soc=soc, suffix=model_suffix
|
||||
),
|
||||
self.model_path,
|
||||
)
|
||||
|
||||
if (config.model.width != 320) or (config.model.height != 320):
|
||||
logger.error(
|
||||
"Make sure to set the model width and heigth to 320 in your config.yml."
|
||||
)
|
||||
raise Exception(
|
||||
"Make sure to set the model width and heigth to 320 in your config.yml."
|
||||
)
|
||||
|
||||
if config.model.input_pixel_format != "bgr":
|
||||
logger.error(
|
||||
'Make sure to set the model input_pixel_format to "bgr" in your config.yml.'
|
||||
)
|
||||
raise Exception(
|
||||
'Make sure to set the model input_pixel_format to "bgr" in your config.yml.'
|
||||
)
|
||||
|
||||
if config.model.input_tensor != "nhwc":
|
||||
logger.error(
|
||||
'Make sure to set the model input_tensor to "nhwc" in your config.yml.'
|
||||
)
|
||||
raise Exception(
|
||||
'Make sure to set the model input_tensor to "nhwc" in your config.yml.'
|
||||
)
|
||||
|
||||
from rknnlite.api import RKNNLite
|
||||
|
||||
self.rknn = RKNNLite(verbose=False)
|
||||
if self.rknn.load_rknn(self.model_path) != 0:
|
||||
logger.error("Error initializing rknn model.")
|
||||
if self.rknn.init_runtime() != 0:
|
||||
logger.error("Error initializing rknn runtime.")
|
||||
if self.rknn.init_runtime(core_mask=self.core_mask) != 0:
|
||||
logger.error(
|
||||
"Error initializing rknn runtime. Do you run docker in privileged mode?"
|
||||
)
|
||||
|
||||
def __del__(self):
|
||||
self.rknn.release()
|
||||
@@ -67,45 +147,43 @@ class Rknn(DetectionApi):
|
||||
"""
|
||||
|
||||
results = np.transpose(results[0, :, :, 0]) # array shape (2100, 84)
|
||||
classes = np.argmax(
|
||||
results[:, 4:], axis=1
|
||||
) # array shape (2100,); index of class with max confidence of each row
|
||||
scores = np.max(
|
||||
results[:, 4:], axis=1
|
||||
) # array shape (2100,); max confidence of each row
|
||||
|
||||
# array shape (2100, 4); bounding box of each row
|
||||
# remove lines with score scores < 0.4
|
||||
filtered_arg = np.argwhere(scores > 0.4)
|
||||
results = results[filtered_arg[:, 0]]
|
||||
scores = scores[filtered_arg[:, 0]]
|
||||
|
||||
num_detections = len(scores)
|
||||
|
||||
if num_detections == 0:
|
||||
return np.zeros((20, 6), np.float32)
|
||||
|
||||
if num_detections > 20:
|
||||
top_arg = np.argpartition(scores, -20)[-20:]
|
||||
results = results[top_arg]
|
||||
scores = scores[top_arg]
|
||||
num_detections = 20
|
||||
|
||||
classes = np.argmax(results[:, 4:], axis=1)
|
||||
|
||||
boxes = np.transpose(
|
||||
np.vstack(
|
||||
(
|
||||
results[:, 0] - 0.5 * results[:, 2],
|
||||
results[:, 1] - 0.5 * results[:, 3],
|
||||
results[:, 2],
|
||||
results[:, 3],
|
||||
(results[:, 1] - 0.5 * results[:, 3]) / self.height,
|
||||
(results[:, 0] - 0.5 * results[:, 2]) / self.width,
|
||||
(results[:, 1] + 0.5 * results[:, 3]) / self.height,
|
||||
(results[:, 0] + 0.5 * results[:, 2]) / self.width,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# indices of rows with confidence > SCORE_THRESH with Non-maximum Suppression (NMS)
|
||||
result_boxes = cv2.dnn.NMSBoxes(
|
||||
boxes, scores, self.score_thresh, self.nms_thresh, 0.5
|
||||
)
|
||||
|
||||
detections = np.zeros((20, 6), np.float32)
|
||||
|
||||
for i in range(len(result_boxes)):
|
||||
if i >= 20:
|
||||
break
|
||||
|
||||
index = result_boxes[i]
|
||||
detections[i] = [
|
||||
classes[index],
|
||||
scores[index],
|
||||
(boxes[index][1]) / self.height,
|
||||
(boxes[index][0]) / self.width,
|
||||
(boxes[index][1] + boxes[index][3]) / self.height,
|
||||
(boxes[index][0] + boxes[index][2]) / self.width,
|
||||
]
|
||||
detections[:num_detections, 0] = classes
|
||||
detections[:num_detections, 1] = scores
|
||||
detections[:num_detections, 2:] = boxes
|
||||
|
||||
return detections
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ _user_agent_args = [
|
||||
|
||||
PRESETS_HW_ACCEL_DECODE = {
|
||||
"preset-rpi-64-h264": "-c:v:1 h264_v4l2m2m",
|
||||
"preset-rpi-64-h265": "-c:v:1 hevc_v4l2m2m",
|
||||
"preset-vaapi": f"-hwaccel_flags allow_profile_mismatch -hwaccel vaapi -hwaccel_device {_gpu_selector.get_selected_gpu()} -hwaccel_output_format vaapi",
|
||||
"preset-intel-qsv-h264": f"-hwaccel qsv -qsv_device {_gpu_selector.get_selected_gpu()} -hwaccel_output_format qsv -c:v h264_qsv",
|
||||
"preset-intel-qsv-h265": f"-load_plugin hevc_hw -hwaccel qsv -qsv_device {_gpu_selector.get_selected_gpu()} -hwaccel_output_format qsv -c:v hevc_qsv",
|
||||
@@ -64,22 +65,28 @@ PRESETS_HW_ACCEL_DECODE = {
|
||||
"preset-nvidia-mjpeg": "-hwaccel cuda -hwaccel_output_format cuda",
|
||||
"preset-jetson-h264": "-c:v h264_nvmpi -resize {1}x{2}",
|
||||
"preset-jetson-h265": "-c:v hevc_nvmpi -resize {1}x{2}",
|
||||
"preset-rk-h264": "-c:v h264_rkmpp_decoder",
|
||||
"preset-rk-h265": "-c:v hevc_rkmpp_decoder",
|
||||
}
|
||||
|
||||
PRESETS_HW_ACCEL_SCALE = {
|
||||
"preset-rpi-64-h264": "-r {0} -vf fps={0},scale={1}:{2}",
|
||||
"preset-vaapi": "-r {0} -vf fps={0},scale_vaapi=w={1}:h={2},hwdownload,format=yuv420p",
|
||||
"preset-rpi-64-h265": "-r {0} -vf fps={0},scale={1}:{2}",
|
||||
"preset-vaapi": "-r {0} -vf fps={0},scale_vaapi=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p",
|
||||
"preset-intel-qsv-h264": "-r {0} -vf vpp_qsv=framerate={0}:w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p",
|
||||
"preset-intel-qsv-h265": "-r {0} -vf vpp_qsv=framerate={0}:w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p",
|
||||
"preset-nvidia-h264": "-r {0} -vf fps={0},scale_cuda=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p",
|
||||
"preset-nvidia-h265": "-r {0} -vf fps={0},scale_cuda=w={1}:h={2}:format=nv12,hwdownload,format=nv12,format=yuv420p",
|
||||
"preset-jetson-h264": "-r {0}", # scaled in decoder
|
||||
"preset-jetson-h265": "-r {0}", # scaled in decoder
|
||||
"preset-rk-h264": "-r {0} -vf fps={0},scale={1}:{2}",
|
||||
"preset-rk-h265": "-r {0} -vf fps={0},scale={1}:{2}",
|
||||
"default": "-r {0} -vf fps={0},scale={1}:{2}",
|
||||
}
|
||||
|
||||
PRESETS_HW_ACCEL_ENCODE_BIRDSEYE = {
|
||||
"preset-rpi-64-h264": "ffmpeg -hide_banner {0} -c:v h264_v4l2m2m {1}",
|
||||
"preset-rpi-64-h265": "ffmpeg -hide_banner {0} -c:v hevc_v4l2m2m {1}",
|
||||
"preset-vaapi": "ffmpeg -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_device {2} {0} -c:v h264_vaapi -g 50 -bf 0 -profile:v high -level:v 4.1 -sei:v 0 -an -vf format=vaapi|nv12,hwupload {1}",
|
||||
"preset-intel-qsv-h264": "ffmpeg -hide_banner {0} -c:v h264_qsv -g 50 -bf 0 -profile:v high -level:v 4.1 -async_depth:v 1 {1}",
|
||||
"preset-intel-qsv-h265": "ffmpeg -hide_banner {0} -c:v h264_qsv -g 50 -bf 0 -profile:v high -level:v 4.1 -async_depth:v 1 {1}",
|
||||
@@ -87,11 +94,14 @@ PRESETS_HW_ACCEL_ENCODE_BIRDSEYE = {
|
||||
"preset-nvidia-h265": "ffmpeg -hide_banner {0} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {1}",
|
||||
"preset-jetson-h264": "ffmpeg -hide_banner {0} -c:v h264_nvmpi -profile high {1}",
|
||||
"preset-jetson-h265": "ffmpeg -hide_banner {0} -c:v h264_nvmpi -profile high {1}",
|
||||
"preset-rk-h264": "ffmpeg -hide_banner {0} -c:v h264_rkmpp_encoder -profile high {1}",
|
||||
"preset-rk-h265": "ffmpeg -hide_banner {0} -c:v hevc_rkmpp_encoder -profile high {1}",
|
||||
"default": "ffmpeg -hide_banner {0} -c:v libx264 -g 50 -profile:v high -level:v 4.1 -preset:v superfast -tune:v zerolatency {1}",
|
||||
}
|
||||
|
||||
PRESETS_HW_ACCEL_ENCODE_TIMELAPSE = {
|
||||
"preset-rpi-64-h264": "ffmpeg -hide_banner {0} -c:v h264_v4l2m2m -pix_fmt yuv420p {1}",
|
||||
"preset-rpi-64-h265": "ffmpeg -hide_banner {0} -c:v hevc_v4l2m2m -pix_fmt yuv420p {1}",
|
||||
"preset-vaapi": "ffmpeg -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_device {2} {0} -c:v h264_vaapi {1}",
|
||||
"preset-intel-qsv-h264": "ffmpeg -hide_banner {0} -c:v h264_qsv -profile:v high -level:v 4.1 -async_depth:v 1 {1}",
|
||||
"preset-intel-qsv-h265": "ffmpeg -hide_banner {0} -c:v hevc_qsv -profile:v high -level:v 4.1 -async_depth:v 1 {1}",
|
||||
@@ -99,6 +109,8 @@ PRESETS_HW_ACCEL_ENCODE_TIMELAPSE = {
|
||||
"preset-nvidia-h265": "ffmpeg -hide_banner -hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 8 {0} -c:v hevc_nvenc {1}",
|
||||
"preset-jetson-h264": "ffmpeg -hide_banner {0} -c:v h264_nvmpi -profile high {1}",
|
||||
"preset-jetson-h265": "ffmpeg -hide_banner {0} -c:v hevc_nvmpi -profile high {1}",
|
||||
"preset-rk-h264": "ffmpeg -hide_banner {0} -c:v h264_rkmpp_encoder -profile high {1}",
|
||||
"preset-rk-h265": "ffmpeg -hide_banner {0} -c:v hevc_rkmpp_encoder -profile high {1}",
|
||||
"default": "ffmpeg -hide_banner {0} -c:v libx264 -preset:v ultrafast -tune:v zerolatency {1}",
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import glob
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import subprocess as sp
|
||||
import time
|
||||
import traceback
|
||||
@@ -755,6 +756,20 @@ def grid_snapshot(camera_name):
|
||||
500,
|
||||
)
|
||||
|
||||
color_arg = request.args.get("color", default="", type=str).lower()
|
||||
draw_font_scale = request.args.get("font_scale", default=0.5, type=float)
|
||||
|
||||
if color_arg == "red":
|
||||
draw_color = (0, 0, 255)
|
||||
elif color_arg == "blue":
|
||||
draw_color = (255, 0, 0)
|
||||
elif color_arg == "black":
|
||||
draw_color = (0, 0, 0)
|
||||
elif color_arg == "white":
|
||||
draw_color = (255, 255, 255)
|
||||
else:
|
||||
draw_color = (0, 255, 0)
|
||||
|
||||
grid_size = len(grid)
|
||||
grid_coef = 1.0 / grid_size
|
||||
width = detect.width
|
||||
@@ -775,7 +790,7 @@ def grid_snapshot(camera_name):
|
||||
int((x + 1) * grid_coef * width),
|
||||
int((y + 1) * grid_coef * height),
|
||||
),
|
||||
(0, 255, 0),
|
||||
draw_color,
|
||||
2,
|
||||
)
|
||||
cv2.putText(
|
||||
@@ -786,8 +801,8 @@ def grid_snapshot(camera_name):
|
||||
int((y * grid_coef + 0.02) * height),
|
||||
),
|
||||
cv2.FONT_HERSHEY_SIMPLEX,
|
||||
fontScale=0.5,
|
||||
color=(0, 255, 0),
|
||||
fontScale=draw_font_scale,
|
||||
color=draw_color,
|
||||
thickness=2,
|
||||
)
|
||||
cv2.putText(
|
||||
@@ -798,8 +813,8 @@ def grid_snapshot(camera_name):
|
||||
int((y * grid_coef + 0.05) * height),
|
||||
),
|
||||
cv2.FONT_HERSHEY_SIMPLEX,
|
||||
fontScale=0.5,
|
||||
color=(0, 255, 0),
|
||||
fontScale=draw_font_scale,
|
||||
color=draw_color,
|
||||
thickness=2,
|
||||
)
|
||||
cv2.putText(
|
||||
@@ -810,8 +825,8 @@ def grid_snapshot(camera_name):
|
||||
int((y * grid_coef + 0.08) * height),
|
||||
),
|
||||
cv2.FONT_HERSHEY_SIMPLEX,
|
||||
fontScale=0.5,
|
||||
color=(0, 255, 0),
|
||||
fontScale=draw_font_scale,
|
||||
color=draw_color,
|
||||
thickness=2,
|
||||
)
|
||||
|
||||
@@ -861,7 +876,7 @@ def event_clip(id):
|
||||
response.headers["Content-Length"] = os.path.getsize(clip_path)
|
||||
response.headers[
|
||||
"X-Accel-Redirect"
|
||||
] = f"/clips/{file_name}" # nginx: http://wiki.nginx.org/NginxXSendfile
|
||||
] = f"/clips/{file_name}" # nginx: https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ignore_headers
|
||||
|
||||
return response
|
||||
|
||||
@@ -1738,7 +1753,7 @@ def recording_clip(camera_name, start_ts, end_ts):
|
||||
response.headers["Content-Length"] = os.path.getsize(path)
|
||||
response.headers[
|
||||
"X-Accel-Redirect"
|
||||
] = f"/cache/{file_name}" # nginx: http://wiki.nginx.org/NginxXSendfile
|
||||
] = f"/cache/{file_name}" # nginx: https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ignore_headers
|
||||
|
||||
return response
|
||||
|
||||
@@ -1940,9 +1955,68 @@ def export_recording(camera_name: str, start_time, end_time):
|
||||
)
|
||||
|
||||
|
||||
def export_filename_check_extension(filename: str):
|
||||
if filename.endswith(".mp4"):
|
||||
return filename
|
||||
else:
|
||||
return filename + ".mp4"
|
||||
|
||||
|
||||
def export_filename_is_valid(filename: str):
|
||||
if re.search(r"[^:_A-Za-z0-9]", filename) or filename.startswith("in_progress."):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
@bp.route("/export/<file_name_current>/<file_name_new>", methods=["PATCH"])
|
||||
def export_rename(file_name_current, file_name_new: str):
|
||||
safe_file_name_current = secure_filename(
|
||||
export_filename_check_extension(file_name_current)
|
||||
)
|
||||
file_current = os.path.join(EXPORT_DIR, safe_file_name_current)
|
||||
|
||||
if not os.path.exists(file_current):
|
||||
return make_response(
|
||||
jsonify({"success": False, "message": f"{file_name_current} not found."}),
|
||||
404,
|
||||
)
|
||||
|
||||
if not export_filename_is_valid(file_name_new):
|
||||
return make_response(
|
||||
jsonify(
|
||||
{
|
||||
"success": False,
|
||||
"message": f"{file_name_new} contains illegal characters.",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
safe_file_name_new = secure_filename(export_filename_check_extension(file_name_new))
|
||||
file_new = os.path.join(EXPORT_DIR, safe_file_name_new)
|
||||
|
||||
if os.path.exists(file_new):
|
||||
return make_response(
|
||||
jsonify({"success": False, "message": f"{file_name_new} already exists."}),
|
||||
400,
|
||||
)
|
||||
|
||||
os.rename(file_current, file_new)
|
||||
return make_response(
|
||||
jsonify(
|
||||
{
|
||||
"success": True,
|
||||
"message": "Successfully renamed file.",
|
||||
}
|
||||
),
|
||||
200,
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/export/<file_name>", methods=["DELETE"])
|
||||
def export_delete(file_name: str):
|
||||
safe_file_name = secure_filename(file_name)
|
||||
safe_file_name = secure_filename(export_filename_check_extension(file_name))
|
||||
file = os.path.join(EXPORT_DIR, safe_file_name)
|
||||
|
||||
if not os.path.exists(file):
|
||||
|
||||
@@ -150,7 +150,9 @@ class PtzAutoTrackerThread(threading.Thread):
|
||||
) -> None:
|
||||
threading.Thread.__init__(self)
|
||||
self.name = "ptz_autotracker"
|
||||
self.ptz_autotracker = PtzAutoTracker(config, onvif, ptz_metrics, dispatcher)
|
||||
self.ptz_autotracker = PtzAutoTracker(
|
||||
config, onvif, ptz_metrics, dispatcher, stop_event
|
||||
)
|
||||
self.stop_event = stop_event
|
||||
self.config = config
|
||||
|
||||
@@ -178,11 +180,13 @@ class PtzAutoTracker:
|
||||
onvif: OnvifController,
|
||||
ptz_metrics: PTZMetricsTypes,
|
||||
dispatcher: Dispatcher,
|
||||
stop_event: MpEvent,
|
||||
) -> None:
|
||||
self.config = config
|
||||
self.onvif = onvif
|
||||
self.ptz_metrics = ptz_metrics
|
||||
self.dispatcher = dispatcher
|
||||
self.stop_event = stop_event
|
||||
self.tracked_object: dict[str, object] = {}
|
||||
self.tracked_object_history: dict[str, object] = {}
|
||||
self.tracked_object_metrics: dict[str, object] = {}
|
||||
@@ -204,7 +208,10 @@ class PtzAutoTracker:
|
||||
continue
|
||||
|
||||
self.autotracker_init[camera] = False
|
||||
if camera_config.onvif.autotracking.enabled:
|
||||
if (
|
||||
camera_config.onvif.autotracking.enabled
|
||||
and camera_config.onvif.autotracking.enabled_in_config
|
||||
):
|
||||
self._autotracker_setup(camera_config, camera)
|
||||
|
||||
def _autotracker_setup(self, camera_config, camera):
|
||||
@@ -581,8 +588,11 @@ class PtzAutoTracker:
|
||||
camera_config.frame_shape[1]
|
||||
camera_config.frame_shape[0]
|
||||
|
||||
while True:
|
||||
move_data = self.move_queues[camera].get()
|
||||
while not self.stop_event.is_set():
|
||||
try:
|
||||
move_data = self.move_queues[camera].get(True, 0.1)
|
||||
except queue.Empty:
|
||||
continue
|
||||
|
||||
with self.move_queue_locks[camera]:
|
||||
frame_time, pan, tilt, zoom = move_data
|
||||
|
||||
@@ -133,6 +133,7 @@ class OnvifController:
|
||||
# setup relative moving request for autotracking
|
||||
move_request = ptz.create_type("RelativeMove")
|
||||
move_request.ProfileToken = profile.token
|
||||
logger.debug(f"{camera_name}: Relative move request: {move_request}")
|
||||
if move_request.Translation is None and fov_space_id is not None:
|
||||
move_request.Translation = status.Position
|
||||
move_request.Translation.PanTilt.space = ptz_config["Spaces"][
|
||||
@@ -162,7 +163,10 @@ class OnvifController:
|
||||
)
|
||||
|
||||
if move_request.Speed is None:
|
||||
move_request.Speed = status.Position if status else None
|
||||
move_request.Speed = configs.DefaultPTZSpeed if configs else None
|
||||
logger.debug(
|
||||
f"{camera_name}: Relative move request after setup: {move_request}"
|
||||
)
|
||||
self.cams[camera_name]["relative_move_request"] = move_request
|
||||
|
||||
# setup absolute moving request for autotracking zooming
|
||||
@@ -207,7 +211,9 @@ class OnvifController:
|
||||
self.config.cameras[camera_name].onvif.autotracking.zooming
|
||||
== ZoomingModeEnum.relative
|
||||
):
|
||||
self.config.cameras[camera_name].onvif.autotracking.zooming = False
|
||||
self.config.cameras[
|
||||
camera_name
|
||||
].onvif.autotracking.zooming = ZoomingModeEnum.disabled
|
||||
logger.warning(
|
||||
f"Disabling autotracking zooming for {camera_name}: Relative zoom not supported"
|
||||
)
|
||||
@@ -222,7 +228,9 @@ class OnvifController:
|
||||
self.cams[camera_name]["zoom_limits"] = configs.ZoomLimits
|
||||
except Exception:
|
||||
if self.config.cameras[camera_name].onvif.autotracking.zooming:
|
||||
self.config.cameras[camera_name].onvif.autotracking.zooming = False
|
||||
self.config.cameras[
|
||||
camera_name
|
||||
].onvif.autotracking.zooming = ZoomingModeEnum.disabled
|
||||
logger.warning(
|
||||
f"Disabling autotracking zooming for {camera_name}: Absolute zoom not supported"
|
||||
)
|
||||
|
||||
@@ -176,10 +176,9 @@ class RecordingCleanup(threading.Thread):
|
||||
|
||||
def run(self) -> None:
|
||||
# on startup sync recordings with disk if enabled
|
||||
if self.config.record.sync_on_startup:
|
||||
if self.config.record.sync_recordings:
|
||||
sync_recordings(limited=False)
|
||||
|
||||
next_sync = get_tomorrow_at_time(3)
|
||||
next_sync = get_tomorrow_at_time(3)
|
||||
|
||||
# Expire tmp clips every minute, recordings and clean directories every hour.
|
||||
for counter in itertools.cycle(range(self.config.record.expire_interval)):
|
||||
@@ -189,7 +188,11 @@ class RecordingCleanup(threading.Thread):
|
||||
|
||||
self.clean_tmp_clips()
|
||||
|
||||
if datetime.datetime.now().astimezone(datetime.timezone.utc) > next_sync:
|
||||
if (
|
||||
self.config.record.sync_recordings
|
||||
and datetime.datetime.now().astimezone(datetime.timezone.utc)
|
||||
> next_sync
|
||||
):
|
||||
sync_recordings(limited=True)
|
||||
next_sync = get_tomorrow_at_time(3)
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ from frigate.const import (
|
||||
CACHE_SEGMENT_FORMAT,
|
||||
INSERT_MANY_RECORDINGS,
|
||||
MAX_SEGMENT_DURATION,
|
||||
MAX_SEGMENTS_IN_CACHE,
|
||||
RECORD_DIR,
|
||||
)
|
||||
from frigate.models import Event, Recordings
|
||||
@@ -121,8 +122,8 @@ class RecordingMaintainer(threading.Thread):
|
||||
}
|
||||
)
|
||||
|
||||
# delete all cached files past the most recent 5
|
||||
keep_count = 5
|
||||
# delete all cached files past the most recent MAX_SEGMENTS_IN_CACHE
|
||||
keep_count = MAX_SEGMENTS_IN_CACHE
|
||||
for camera in grouped_recordings.keys():
|
||||
# sort based on start time
|
||||
grouped_recordings[camera] = sorted(
|
||||
@@ -225,12 +226,8 @@ class RecordingMaintainer(threading.Thread):
|
||||
|
||||
# if cached file's start_time is earlier than the retain days for the camera
|
||||
if start_time <= (
|
||||
(
|
||||
datetime.datetime.now().astimezone(datetime.timezone.utc)
|
||||
- datetime.timedelta(
|
||||
days=self.config.cameras[camera].record.retain.days
|
||||
)
|
||||
)
|
||||
datetime.datetime.now().astimezone(datetime.timezone.utc)
|
||||
- datetime.timedelta(days=self.config.cameras[camera].record.retain.days)
|
||||
):
|
||||
# if the cached segment overlaps with the events:
|
||||
overlaps = False
|
||||
|
||||
@@ -31,13 +31,12 @@ def remove_empty_directories(directory: str) -> None:
|
||||
def sync_recordings(limited: bool) -> None:
|
||||
"""Check the db for stale recordings entries that don't exist in the filesystem."""
|
||||
|
||||
def delete_db_entries_without_file(files_on_disk: list[str]) -> bool:
|
||||
def delete_db_entries_without_file(check_timestamp: float) -> bool:
|
||||
"""Delete db entries where file was deleted outside of frigate."""
|
||||
|
||||
if limited:
|
||||
recordings = Recordings.select(Recordings.id, Recordings.path).where(
|
||||
Recordings.start_time
|
||||
>= (datetime.datetime.now() - datetime.timedelta(hours=36)).timestamp()
|
||||
Recordings.start_time >= check_timestamp
|
||||
)
|
||||
else:
|
||||
# get all recordings in the db
|
||||
@@ -50,9 +49,16 @@ def sync_recordings(limited: bool) -> None:
|
||||
|
||||
for page in range(num_pages):
|
||||
for recording in recordings.paginate(page, page_size):
|
||||
if recording.path not in files_on_disk:
|
||||
if not os.path.exists(recording.path):
|
||||
recordings_to_delete.add(recording.id)
|
||||
|
||||
if len(recordings_to_delete) == 0:
|
||||
return True
|
||||
|
||||
logger.info(
|
||||
f"Deleting {len(recordings_to_delete)} recording DB entries with missing files"
|
||||
)
|
||||
|
||||
# convert back to list of dictionaries for insertion
|
||||
recordings_to_delete = [
|
||||
{"id": recording_id} for recording_id in recordings_to_delete
|
||||
@@ -64,10 +70,6 @@ def sync_recordings(limited: bool) -> None:
|
||||
)
|
||||
return False
|
||||
|
||||
logger.debug(
|
||||
f"Deleting {len(recordings_to_delete)} recording DB entries with missing files"
|
||||
)
|
||||
|
||||
# create a temporary table for deletion
|
||||
RecordingsToDelete.create_table(temporary=True)
|
||||
|
||||
@@ -95,38 +97,51 @@ def sync_recordings(limited: bool) -> None:
|
||||
if not Recordings.select().where(Recordings.path == file).exists():
|
||||
files_to_delete.append(file)
|
||||
|
||||
if len(files_to_delete) == 0:
|
||||
return True
|
||||
|
||||
logger.info(
|
||||
f"Deleting {len(files_to_delete)} recordings files with missing DB entries"
|
||||
)
|
||||
|
||||
if float(len(files_to_delete)) / max(1, len(files_on_disk)) > 0.5:
|
||||
logger.debug(
|
||||
f"Deleting {(float(len(files_to_delete)) / len(files_on_disk)):2f}% of recordings DB entries, could be due to configuration error. Aborting..."
|
||||
)
|
||||
return
|
||||
return False
|
||||
|
||||
for file in files_to_delete:
|
||||
os.unlink(file)
|
||||
|
||||
return True
|
||||
|
||||
logger.debug("Start sync recordings.")
|
||||
|
||||
if limited:
|
||||
# get recording files from last 36 hours
|
||||
hour_check = f"{RECORD_DIR}/{(datetime.datetime.now().astimezone(datetime.timezone.utc) - datetime.timedelta(hours=36)).strftime('%Y-%m-%d/%H')}"
|
||||
files_on_disk = {
|
||||
os.path.join(root, file)
|
||||
for root, _, files in os.walk(RECORD_DIR)
|
||||
for file in files
|
||||
if root > hour_check
|
||||
}
|
||||
else:
|
||||
# get all recordings files on disk and put them in a set
|
||||
files_on_disk = {
|
||||
os.path.join(root, file)
|
||||
for root, _, files in os.walk(RECORD_DIR)
|
||||
for file in files
|
||||
}
|
||||
|
||||
db_success = delete_db_entries_without_file(files_on_disk)
|
||||
# start checking on the hour 36 hours ago
|
||||
check_point = datetime.datetime.now().replace(
|
||||
minute=0, second=0, microsecond=0
|
||||
).astimezone(datetime.timezone.utc) - datetime.timedelta(hours=36)
|
||||
db_success = delete_db_entries_without_file(check_point.timestamp())
|
||||
|
||||
# only try to cleanup files if db cleanup was successful
|
||||
if db_success:
|
||||
if limited:
|
||||
# get recording files from last 36 hours
|
||||
hour_check = f"{RECORD_DIR}/{check_point.strftime('%Y-%m-%d/%H')}"
|
||||
files_on_disk = {
|
||||
os.path.join(root, file)
|
||||
for root, _, files in os.walk(RECORD_DIR)
|
||||
for file in files
|
||||
if root > hour_check
|
||||
}
|
||||
else:
|
||||
# get all recordings files on disk and put them in a set
|
||||
files_on_disk = {
|
||||
os.path.join(root, file)
|
||||
for root, _, files in os.walk(RECORD_DIR)
|
||||
for file in files
|
||||
}
|
||||
|
||||
delete_files_without_db_entry(files_on_disk)
|
||||
|
||||
logger.debug("End sync recordings.")
|
||||
|
||||
@@ -5,7 +5,7 @@ import numpy as np
|
||||
from norfair.drawing.color import Palette
|
||||
from norfair.drawing.drawer import Drawer
|
||||
|
||||
from frigate.util.image import intersection
|
||||
from frigate.util.image import intersection, transliterate_to_latin
|
||||
from frigate.util.object import (
|
||||
get_cluster_boundary,
|
||||
get_cluster_candidates,
|
||||
@@ -82,6 +82,11 @@ class TestRegion(unittest.TestCase):
|
||||
|
||||
assert len(cluster_candidates) == 2
|
||||
|
||||
def test_transliterate_to_latin(self):
|
||||
self.assertEqual(transliterate_to_latin("frégate"), "fregate")
|
||||
self.assertEqual(transliterate_to_latin("utilité"), "utilite")
|
||||
self.assertEqual(transliterate_to_latin("imágé"), "image")
|
||||
|
||||
def test_cluster_boundary(self):
|
||||
boxes = [(100, 100, 200, 200), (215, 215, 325, 325)]
|
||||
boundary_boxes = [
|
||||
|
||||
@@ -16,6 +16,7 @@ import pytz
|
||||
import yaml
|
||||
from ruamel.yaml import YAML
|
||||
from tzlocal import get_localzone
|
||||
from zoneinfo import ZoneInfoNotFoundError
|
||||
|
||||
from frigate.const import REGEX_HTTP_CAMERA_USER_PASS, REGEX_RTSP_CAMERA_USER_PASS
|
||||
|
||||
@@ -266,7 +267,16 @@ def find_by_key(dictionary, target_key):
|
||||
|
||||
def get_tomorrow_at_time(hour: int) -> datetime.datetime:
|
||||
"""Returns the datetime of the following day at 2am."""
|
||||
tomorrow = datetime.datetime.now(get_localzone()) + datetime.timedelta(days=1)
|
||||
try:
|
||||
tomorrow = datetime.datetime.now(get_localzone()) + datetime.timedelta(days=1)
|
||||
except ZoneInfoNotFoundError:
|
||||
tomorrow = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
|
||||
days=1
|
||||
)
|
||||
logger.warning(
|
||||
"Using utc for maintenance due to missing or incorrect timezone set"
|
||||
)
|
||||
|
||||
return tomorrow.replace(hour=hour, minute=0, second=0).astimezone(
|
||||
datetime.timezone.utc
|
||||
)
|
||||
|
||||
@@ -9,10 +9,32 @@ from typing import AnyStr, Optional
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from unidecode import unidecode
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def transliterate_to_latin(text: str) -> str:
|
||||
"""
|
||||
Transliterate a given text to Latin.
|
||||
|
||||
This function uses the unidecode library to transliterate the input text to Latin.
|
||||
It is useful for converting texts with diacritics or non-Latin characters to a
|
||||
Latin equivalent.
|
||||
|
||||
Args:
|
||||
text (str): The text to be transliterated.
|
||||
|
||||
Returns:
|
||||
str: The transliterated text.
|
||||
|
||||
Example:
|
||||
>>> transliterate_to_latin('frégate')
|
||||
'fregate'
|
||||
"""
|
||||
return unidecode(text)
|
||||
|
||||
|
||||
def draw_timestamp(
|
||||
frame,
|
||||
timestamp,
|
||||
@@ -116,7 +138,10 @@ def draw_box_with_label(
|
||||
):
|
||||
if color is None:
|
||||
color = (0, 0, 255)
|
||||
display_text = "{}: {}".format(label, info)
|
||||
try:
|
||||
display_text = transliterate_to_latin("{}: {}".format(label, info))
|
||||
except Exception:
|
||||
display_text = "{}: {}".format(label, info)
|
||||
cv2.rectangle(frame, (x_min, y_min), (x_max, y_max), color, thickness)
|
||||
font_scale = 0.5
|
||||
font = cv2.FONT_HERSHEY_SIMPLEX
|
||||
@@ -287,17 +312,14 @@ def yuv_crop_and_resize(frame, region, height=None):
|
||||
# copy u2
|
||||
yuv_cropped_frame[
|
||||
size + uv_channel_y_offset : size + uv_channel_y_offset + uv_crop_height,
|
||||
size // 2
|
||||
+ uv_channel_x_offset : size // 2
|
||||
size // 2 + uv_channel_x_offset : size // 2
|
||||
+ uv_channel_x_offset
|
||||
+ uv_crop_width,
|
||||
] = frame[u2[1] : u2[3], u2[0] : u2[2]]
|
||||
|
||||
# copy v1
|
||||
yuv_cropped_frame[
|
||||
size
|
||||
+ size // 4
|
||||
+ uv_channel_y_offset : size
|
||||
size + size // 4 + uv_channel_y_offset : size
|
||||
+ size // 4
|
||||
+ uv_channel_y_offset
|
||||
+ uv_crop_height,
|
||||
@@ -306,14 +328,11 @@ def yuv_crop_and_resize(frame, region, height=None):
|
||||
|
||||
# copy v2
|
||||
yuv_cropped_frame[
|
||||
size
|
||||
+ size // 4
|
||||
+ uv_channel_y_offset : size
|
||||
size + size // 4 + uv_channel_y_offset : size
|
||||
+ size // 4
|
||||
+ uv_channel_y_offset
|
||||
+ uv_crop_height,
|
||||
size // 2
|
||||
+ uv_channel_x_offset : size // 2
|
||||
size // 2 + uv_channel_x_offset : size // 2
|
||||
+ uv_channel_x_offset
|
||||
+ uv_crop_width,
|
||||
] = frame[v2[1] : v2[3], v2[0] : v2[2]]
|
||||
|
||||
7
netlify.toml
Normal file
7
netlify.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[build]
|
||||
base = "docs/"
|
||||
publish = "build"
|
||||
command = "npm run build"
|
||||
environment = { NODE_VERSION = "20" }
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
|
||||
[tool.ruff]
|
||||
ignore = ["E501","E711","E712"]
|
||||
ignore = ["E501","E711","E712"]
|
||||
extend-select = ["I"]
|
||||
286
web/package-lock.json
generated
286
web/package-lock.json
generated
@@ -928,9 +928,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz",
|
||||
"integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz",
|
||||
"integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.4",
|
||||
@@ -951,9 +951,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/globals": {
|
||||
"version": "13.21.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz",
|
||||
"integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==",
|
||||
"version": "13.23.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz",
|
||||
"integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"type-fest": "^0.20.2"
|
||||
@@ -978,9 +978,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "8.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz",
|
||||
"integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz",
|
||||
"integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
@@ -1225,9 +1225,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@preact/preset-vite": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.6.0.tgz",
|
||||
"integrity": "sha512-5nztNzXbCpqyVum/K94nB2YQ5PTnvWdz4u7/X0jc8+kLyskSSpkNUxLQJeI90zfGSFIX1Ibj2G2JIS/mySHWYQ==",
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.7.0.tgz",
|
||||
"integrity": "sha512-m5N0FVtxbCCDxNk55NGhsRpKJChYcupcuQHzMJc/Bll07IKZKn8amwYciyKFS9haU6AgzDAJ/ewvApr6Qg1DHw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-react-jsx": "^7.22.15",
|
||||
@@ -1241,7 +1241,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "7.x",
|
||||
"vite": "2.x || 3.x || 4.x"
|
||||
"vite": "2.x || 3.x || 4.x || 5.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@prefresh/babel-plugin": {
|
||||
@@ -1302,9 +1302,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@tailwindcss/forms": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.6.tgz",
|
||||
"integrity": "sha512-Fw+2BJ0tmAwK/w01tEFL5TiaJBX1NLT1/YbWgvm7ws3Qcn11kiXxzNTEQDMs5V3mQemhB56l3u0i9dwdzSQldA==",
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz",
|
||||
"integrity": "sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mini-svg-data-uri": "^1.2.3"
|
||||
@@ -1664,16 +1664,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.9.1.tgz",
|
||||
"integrity": "sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==",
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.11.0.tgz",
|
||||
"integrity": "sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.5.1",
|
||||
"@typescript-eslint/scope-manager": "6.9.1",
|
||||
"@typescript-eslint/type-utils": "6.9.1",
|
||||
"@typescript-eslint/utils": "6.9.1",
|
||||
"@typescript-eslint/visitor-keys": "6.9.1",
|
||||
"@typescript-eslint/scope-manager": "6.11.0",
|
||||
"@typescript-eslint/type-utils": "6.11.0",
|
||||
"@typescript-eslint/utils": "6.11.0",
|
||||
"@typescript-eslint/visitor-keys": "6.11.0",
|
||||
"debug": "^4.3.4",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^5.2.4",
|
||||
@@ -1870,15 +1870,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.1.tgz",
|
||||
"integrity": "sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==",
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.11.0.tgz",
|
||||
"integrity": "sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "6.9.1",
|
||||
"@typescript-eslint/types": "6.9.1",
|
||||
"@typescript-eslint/typescript-estree": "6.9.1",
|
||||
"@typescript-eslint/visitor-keys": "6.9.1",
|
||||
"@typescript-eslint/scope-manager": "6.11.0",
|
||||
"@typescript-eslint/types": "6.11.0",
|
||||
"@typescript-eslint/typescript-estree": "6.11.0",
|
||||
"@typescript-eslint/visitor-keys": "6.11.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1898,13 +1898,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.9.1.tgz",
|
||||
"integrity": "sha512-38IxvKB6NAne3g/+MyXMs2Cda/Sz+CEpmm+KLGEM8hx/CvnSRuw51i8ukfwB/B/sESdeTGet1NH1Wj7I0YXswg==",
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.11.0.tgz",
|
||||
"integrity": "sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "6.9.1",
|
||||
"@typescript-eslint/visitor-keys": "6.9.1"
|
||||
"@typescript-eslint/types": "6.11.0",
|
||||
"@typescript-eslint/visitor-keys": "6.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.0.0 || >=18.0.0"
|
||||
@@ -1915,13 +1915,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.9.1.tgz",
|
||||
"integrity": "sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==",
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.11.0.tgz",
|
||||
"integrity": "sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "6.9.1",
|
||||
"@typescript-eslint/utils": "6.9.1",
|
||||
"@typescript-eslint/typescript-estree": "6.11.0",
|
||||
"@typescript-eslint/utils": "6.11.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^1.0.1"
|
||||
},
|
||||
@@ -1942,9 +1942,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.9.1.tgz",
|
||||
"integrity": "sha512-BUGslGOb14zUHOUmDB2FfT6SI1CcZEJYfF3qFwBeUrU6srJfzANonwRYHDpLBuzbq3HaoF2XL2hcr01c8f8OaQ==",
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.11.0.tgz",
|
||||
"integrity": "sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^16.0.0 || >=18.0.0"
|
||||
@@ -1955,13 +1955,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.1.tgz",
|
||||
"integrity": "sha512-U+mUylTHfcqeO7mLWVQ5W/tMLXqVpRv61wm9ZtfE5egz7gtnmqVIw9ryh0mgIlkKk9rZLY3UHygsBSdB9/ftyw==",
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.11.0.tgz",
|
||||
"integrity": "sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "6.9.1",
|
||||
"@typescript-eslint/visitor-keys": "6.9.1",
|
||||
"@typescript-eslint/types": "6.11.0",
|
||||
"@typescript-eslint/visitor-keys": "6.11.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
@@ -1997,17 +1997,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.9.1.tgz",
|
||||
"integrity": "sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==",
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.11.0.tgz",
|
||||
"integrity": "sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"@types/json-schema": "^7.0.12",
|
||||
"@types/semver": "^7.5.0",
|
||||
"@typescript-eslint/scope-manager": "6.9.1",
|
||||
"@typescript-eslint/types": "6.9.1",
|
||||
"@typescript-eslint/typescript-estree": "6.9.1",
|
||||
"@typescript-eslint/scope-manager": "6.11.0",
|
||||
"@typescript-eslint/types": "6.11.0",
|
||||
"@typescript-eslint/typescript-estree": "6.11.0",
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2037,12 +2037,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.1.tgz",
|
||||
"integrity": "sha512-MUaPUe/QRLEffARsmNfmpghuQkW436DvESW+h+M52w0coICHRfD6Np9/K6PdACwnrq1HmuLl+cSPZaJmeVPkSw==",
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.11.0.tgz",
|
||||
"integrity": "sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "6.9.1",
|
||||
"@typescript-eslint/types": "6.11.0",
|
||||
"eslint-visitor-keys": "^3.4.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2653,9 +2653,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz",
|
||||
"integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==",
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
|
||||
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
@@ -3667,15 +3667,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "8.52.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz",
|
||||
"integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz",
|
||||
"integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
"@eslint/eslintrc": "^2.1.2",
|
||||
"@eslint/js": "8.52.0",
|
||||
"@eslint/eslintrc": "^2.1.3",
|
||||
"@eslint/js": "8.54.0",
|
||||
"@humanwhocodes/config-array": "^0.11.13",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@nodelib/fs.walk": "^1.2.8",
|
||||
@@ -7666,9 +7666,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/preact": {
|
||||
"version": "10.18.1",
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.18.1.tgz",
|
||||
"integrity": "sha512-mKUD7RRkQQM6s7Rkmi7IFkoEHjuFqRQUaXamO61E6Nn7vqF/bo7EZCmSyrUnp2UWHw0O7XjZ2eeXis+m7tf4lg==",
|
||||
"version": "10.19.2",
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.19.2.tgz",
|
||||
"integrity": "sha512-UA9DX/OJwv6YwP9Vn7Ti/vF80XL+YA5H2l7BpCtUr3ya8LWHFzpiO5R+N7dN16ujpIxhekRFuOOF82bXX7K/lg==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
@@ -7697,9 +7697,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
|
||||
"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz",
|
||||
"integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
@@ -10248,9 +10248,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@eslint/eslintrc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz",
|
||||
"integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz",
|
||||
"integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "^6.12.4",
|
||||
@@ -10265,9 +10265,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"globals": {
|
||||
"version": "13.21.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz",
|
||||
"integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==",
|
||||
"version": "13.23.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz",
|
||||
"integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"type-fest": "^0.20.2"
|
||||
@@ -10282,9 +10282,9 @@
|
||||
}
|
||||
},
|
||||
"@eslint/js": {
|
||||
"version": "8.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz",
|
||||
"integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz",
|
||||
"integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@humanwhocodes/config-array": {
|
||||
@@ -10476,9 +10476,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@preact/preset-vite": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.6.0.tgz",
|
||||
"integrity": "sha512-5nztNzXbCpqyVum/K94nB2YQ5PTnvWdz4u7/X0jc8+kLyskSSpkNUxLQJeI90zfGSFIX1Ibj2G2JIS/mySHWYQ==",
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.7.0.tgz",
|
||||
"integrity": "sha512-m5N0FVtxbCCDxNk55NGhsRpKJChYcupcuQHzMJc/Bll07IKZKn8amwYciyKFS9haU6AgzDAJ/ewvApr6Qg1DHw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/plugin-transform-react-jsx": "^7.22.15",
|
||||
@@ -10540,9 +10540,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@tailwindcss/forms": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.6.tgz",
|
||||
"integrity": "sha512-Fw+2BJ0tmAwK/w01tEFL5TiaJBX1NLT1/YbWgvm7ws3Qcn11kiXxzNTEQDMs5V3mQemhB56l3u0i9dwdzSQldA==",
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz",
|
||||
"integrity": "sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mini-svg-data-uri": "^1.2.3"
|
||||
@@ -10821,16 +10821,16 @@
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.9.1.tgz",
|
||||
"integrity": "sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==",
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.11.0.tgz",
|
||||
"integrity": "sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@eslint-community/regexpp": "^4.5.1",
|
||||
"@typescript-eslint/scope-manager": "6.9.1",
|
||||
"@typescript-eslint/type-utils": "6.9.1",
|
||||
"@typescript-eslint/utils": "6.9.1",
|
||||
"@typescript-eslint/visitor-keys": "6.9.1",
|
||||
"@typescript-eslint/scope-manager": "6.11.0",
|
||||
"@typescript-eslint/type-utils": "6.11.0",
|
||||
"@typescript-eslint/utils": "6.11.0",
|
||||
"@typescript-eslint/visitor-keys": "6.11.0",
|
||||
"debug": "^4.3.4",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^5.2.4",
|
||||
@@ -10944,54 +10944,54 @@
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/parser": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.1.tgz",
|
||||
"integrity": "sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==",
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.11.0.tgz",
|
||||
"integrity": "sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/scope-manager": "6.9.1",
|
||||
"@typescript-eslint/types": "6.9.1",
|
||||
"@typescript-eslint/typescript-estree": "6.9.1",
|
||||
"@typescript-eslint/visitor-keys": "6.9.1",
|
||||
"@typescript-eslint/scope-manager": "6.11.0",
|
||||
"@typescript-eslint/types": "6.11.0",
|
||||
"@typescript-eslint/typescript-estree": "6.11.0",
|
||||
"@typescript-eslint/visitor-keys": "6.11.0",
|
||||
"debug": "^4.3.4"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/scope-manager": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.9.1.tgz",
|
||||
"integrity": "sha512-38IxvKB6NAne3g/+MyXMs2Cda/Sz+CEpmm+KLGEM8hx/CvnSRuw51i8ukfwB/B/sESdeTGet1NH1Wj7I0YXswg==",
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.11.0.tgz",
|
||||
"integrity": "sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "6.9.1",
|
||||
"@typescript-eslint/visitor-keys": "6.9.1"
|
||||
"@typescript-eslint/types": "6.11.0",
|
||||
"@typescript-eslint/visitor-keys": "6.11.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/type-utils": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.9.1.tgz",
|
||||
"integrity": "sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==",
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.11.0.tgz",
|
||||
"integrity": "sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/typescript-estree": "6.9.1",
|
||||
"@typescript-eslint/utils": "6.9.1",
|
||||
"@typescript-eslint/typescript-estree": "6.11.0",
|
||||
"@typescript-eslint/utils": "6.11.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/types": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.9.1.tgz",
|
||||
"integrity": "sha512-BUGslGOb14zUHOUmDB2FfT6SI1CcZEJYfF3qFwBeUrU6srJfzANonwRYHDpLBuzbq3HaoF2XL2hcr01c8f8OaQ==",
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.11.0.tgz",
|
||||
"integrity": "sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==",
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.1.tgz",
|
||||
"integrity": "sha512-U+mUylTHfcqeO7mLWVQ5W/tMLXqVpRv61wm9ZtfE5egz7gtnmqVIw9ryh0mgIlkKk9rZLY3UHygsBSdB9/ftyw==",
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.11.0.tgz",
|
||||
"integrity": "sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "6.9.1",
|
||||
"@typescript-eslint/visitor-keys": "6.9.1",
|
||||
"@typescript-eslint/types": "6.11.0",
|
||||
"@typescript-eslint/visitor-keys": "6.11.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
@@ -11011,17 +11011,17 @@
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/utils": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.9.1.tgz",
|
||||
"integrity": "sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==",
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.11.0.tgz",
|
||||
"integrity": "sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"@types/json-schema": "^7.0.12",
|
||||
"@types/semver": "^7.5.0",
|
||||
"@typescript-eslint/scope-manager": "6.9.1",
|
||||
"@typescript-eslint/types": "6.9.1",
|
||||
"@typescript-eslint/typescript-estree": "6.9.1",
|
||||
"@typescript-eslint/scope-manager": "6.11.0",
|
||||
"@typescript-eslint/types": "6.11.0",
|
||||
"@typescript-eslint/typescript-estree": "6.11.0",
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -11037,12 +11037,12 @@
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/visitor-keys": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.1.tgz",
|
||||
"integrity": "sha512-MUaPUe/QRLEffARsmNfmpghuQkW436DvESW+h+M52w0coICHRfD6Np9/K6PdACwnrq1HmuLl+cSPZaJmeVPkSw==",
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.11.0.tgz",
|
||||
"integrity": "sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "6.9.1",
|
||||
"@typescript-eslint/types": "6.11.0",
|
||||
"eslint-visitor-keys": "^3.4.1"
|
||||
}
|
||||
},
|
||||
@@ -11495,9 +11495,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz",
|
||||
"integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==",
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
|
||||
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
@@ -12247,15 +12247,15 @@
|
||||
"dev": true
|
||||
},
|
||||
"eslint": {
|
||||
"version": "8.52.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz",
|
||||
"integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz",
|
||||
"integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
"@eslint/eslintrc": "^2.1.2",
|
||||
"@eslint/js": "8.52.0",
|
||||
"@eslint/eslintrc": "^2.1.3",
|
||||
"@eslint/js": "8.54.0",
|
||||
"@humanwhocodes/config-array": "^0.11.13",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@nodelib/fs.walk": "^1.2.8",
|
||||
@@ -15049,9 +15049,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"preact": {
|
||||
"version": "10.18.1",
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.18.1.tgz",
|
||||
"integrity": "sha512-mKUD7RRkQQM6s7Rkmi7IFkoEHjuFqRQUaXamO61E6Nn7vqF/bo7EZCmSyrUnp2UWHw0O7XjZ2eeXis+m7tf4lg=="
|
||||
"version": "10.19.2",
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.19.2.tgz",
|
||||
"integrity": "sha512-UA9DX/OJwv6YwP9Vn7Ti/vF80XL+YA5H2l7BpCtUr3ya8LWHFzpiO5R+N7dN16ujpIxhekRFuOOF82bXX7K/lg=="
|
||||
},
|
||||
"preact-async-route": {
|
||||
"version": "2.2.1",
|
||||
@@ -15071,9 +15071,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"prettier": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
|
||||
"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz",
|
||||
"integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==",
|
||||
"dev": true
|
||||
},
|
||||
"prettier-linter-helpers": {
|
||||
|
||||
@@ -223,6 +223,13 @@ const getUTCOffset = (date: Date, timezone: string): number => {
|
||||
// locale of en-CA is required for proper locale format
|
||||
let iso = utcDate.toLocaleString('en-CA', { timeZone: timezone, hour12: false }).replace(', ', 'T');
|
||||
iso += `.${utcDate.getMilliseconds().toString().padStart(3, '0')}`;
|
||||
const target = new Date(`${iso}Z`);
|
||||
let target = new Date(`${iso}Z`);
|
||||
|
||||
// safari doesn't like the default format
|
||||
if (isNaN(target.getTime())) {
|
||||
iso = iso.replace("T", " ").split(".")[0];
|
||||
target = new Date(`${iso}+000`);
|
||||
}
|
||||
|
||||
return (target.getTime() - utcDate.getTime()) / 60 / 1000;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user