Compare commits

...
Author SHA1 Message Date
93d39370b6 update docs to be more clear regarding audio support and go2rtc requi… (#16232)
* update docs to be more clear regarding audio support and go2rtc requirement

Signed-off-by: Ben Clouser <dev@benclouser.com>

* Update docs/docs/troubleshooting/faqs.md

* Update docs/docs/troubleshooting/faqs.md

* Update docs/docs/troubleshooting/faqs.md

* Clarify title

* Cleanup

---------

Signed-off-by: Ben Clouser <dev@benclouser.com>
Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2025-01-30 11:23:38 -07:00
Nicolas MowenandGitHub cea210d800 Fix csrf (#16230)
* Fix csrf check

* Simplify
2025-01-30 11:27:38 -06:00
Josh HawkinsandGitHub 7b65bcf13c Fix interpolation for autotracking cameras (#16211) 2025-01-29 06:44:13 -07:00
Josh HawkinsandGitHub 335b7564d5 Update plus submission docs and remove 0.14 UI image (#16199) 2025-01-28 11:39:12 -06:00
Josh HawkinsandGitHub 202e9ad9ce Document OPENAI_BASE_URL env var (#16195) 2025-01-28 08:58:15 -07:00
Nicolas MowenandGitHub 99d27c154e Don't show sub labels in main label filter list (#16168) 2025-01-27 08:07:49 -06:00
Nicolas MowenandGitHub 5943fc1895 Fix h265 encoding presets (#16158) 2025-01-26 17:14:02 -07:00
Josh HawkinsandGitHub 9efc20e58a Fix selection of tracked objects in Explore on desktop Safari (#16153)
* ensure meta click works on desktop safari to select objects in explore

* don't break mobile
2025-01-26 10:57:38 -07:00
Nicolas MowenandGitHub 6d8234fa27 Fix build (#16119)
* Update to bake v6

* Update setup actions

* Temp

* Use ubuntu 22.04 for build

* Remove temp
2025-01-24 09:45:46 -06:00
Nicolas MowenandGitHub ad76c28a66 Downgrade to bake-action v5 (#16098) 2025-01-23 09:18:15 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
131d07e649 Bump docker/bake-action from 3 to 6 (#15892)
Bumps [docker/bake-action](https://github.com/docker/bake-action) from 3 to 6.
- [Release notes](https://github.com/docker/bake-action/releases)
- [Commits](https://github.com/docker/bake-action/compare/v3...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-23 07:58:42 -07:00
Nicolas MowenandGitHub 776bb79f0b Consider pre and post capture when cleaning up recordings based on review segments (#16096) 2025-01-23 08:26:53 -06:00
Josh HawkinsandGitHub aedfaa3641 Don't prevent default when tracked object details description input is focused (#16064) 2025-01-20 06:23:22 -07:00
16 changed files with 101 additions and 42 deletions
+2 -2
View File
@@ -33,9 +33,9 @@ runs:
with: with:
string: ${{ github.repository }} string: ${{ github.repository }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
- name: Log in to the Container registry - name: Log in to the Container registry
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc
with: with:
+22 -15
View File
@@ -19,7 +19,7 @@ env:
jobs: jobs:
amd64_build: amd64_build:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
name: AMD64 Build name: AMD64 Build
steps: steps:
- name: Check out code - name: Check out code
@@ -42,7 +42,7 @@ jobs:
tags: ${{ steps.setup.outputs.image-name }}-amd64 tags: ${{ steps.setup.outputs.image-name }}-amd64
cache-from: type=registry,ref=${{ steps.setup.outputs.cache-name }}-amd64 cache-from: type=registry,ref=${{ steps.setup.outputs.cache-name }}-amd64
arm64_build: arm64_build:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
name: ARM Build name: ARM Build
steps: steps:
- name: Check out code - name: Check out code
@@ -66,8 +66,9 @@ jobs:
${{ steps.setup.outputs.image-name }}-standard-arm64 ${{ steps.setup.outputs.image-name }}-standard-arm64
cache-from: type=registry,ref=${{ steps.setup.outputs.cache-name }}-arm64 cache-from: type=registry,ref=${{ steps.setup.outputs.cache-name }}-arm64
- name: Build and push RPi build - name: Build and push RPi build
uses: docker/bake-action@v4 uses: docker/bake-action@v6
with: with:
source: .
push: true push: true
targets: rpi targets: rpi
files: docker/rpi/rpi.hcl files: docker/rpi/rpi.hcl
@@ -76,7 +77,7 @@ jobs:
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-arm64 *.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-arm64
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-arm64,mode=max *.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-arm64,mode=max
jetson_jp4_build: jetson_jp4_build:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
name: Jetson Jetpack 4 name: Jetson Jetpack 4
steps: steps:
- name: Check out code - name: Check out code
@@ -94,8 +95,9 @@ jobs:
BASE_IMAGE: timongentzsch/l4t-ubuntu20-opencv:latest BASE_IMAGE: timongentzsch/l4t-ubuntu20-opencv:latest
SLIM_BASE: timongentzsch/l4t-ubuntu20-opencv:latest SLIM_BASE: timongentzsch/l4t-ubuntu20-opencv:latest
TRT_BASE: timongentzsch/l4t-ubuntu20-opencv:latest TRT_BASE: timongentzsch/l4t-ubuntu20-opencv:latest
uses: docker/bake-action@v4 uses: docker/bake-action@v6
with: with:
source: .
push: true push: true
targets: tensorrt targets: tensorrt
files: docker/tensorrt/trt.hcl files: docker/tensorrt/trt.hcl
@@ -104,7 +106,7 @@ jobs:
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp4 *.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp4
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp4,mode=max *.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp4,mode=max
jetson_jp5_build: jetson_jp5_build:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
name: Jetson Jetpack 5 name: Jetson Jetpack 5
steps: steps:
- name: Check out code - name: Check out code
@@ -122,8 +124,9 @@ jobs:
BASE_IMAGE: nvcr.io/nvidia/l4t-tensorrt:r8.5.2-runtime BASE_IMAGE: nvcr.io/nvidia/l4t-tensorrt:r8.5.2-runtime
SLIM_BASE: nvcr.io/nvidia/l4t-tensorrt:r8.5.2-runtime SLIM_BASE: nvcr.io/nvidia/l4t-tensorrt:r8.5.2-runtime
TRT_BASE: nvcr.io/nvidia/l4t-tensorrt:r8.5.2-runtime TRT_BASE: nvcr.io/nvidia/l4t-tensorrt:r8.5.2-runtime
uses: docker/bake-action@v4 uses: docker/bake-action@v6
with: with:
source: .
push: true push: true
targets: tensorrt targets: tensorrt
files: docker/tensorrt/trt.hcl files: docker/tensorrt/trt.hcl
@@ -132,7 +135,7 @@ jobs:
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp5 *.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp5
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp5,mode=max *.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp5,mode=max
amd64_extra_builds: amd64_extra_builds:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
name: AMD64 Extra Build name: AMD64 Extra Build
needs: needs:
- amd64_build - amd64_build
@@ -149,8 +152,9 @@ jobs:
- name: Build and push TensorRT (x86 GPU) - name: Build and push TensorRT (x86 GPU)
env: env:
COMPUTE_LEVEL: "50 60 70 80 90" COMPUTE_LEVEL: "50 60 70 80 90"
uses: docker/bake-action@v4 uses: docker/bake-action@v6
with: with:
source: .
push: true push: true
targets: tensorrt targets: tensorrt
files: docker/tensorrt/trt.hcl files: docker/tensorrt/trt.hcl
@@ -159,7 +163,7 @@ jobs:
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-amd64 *.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-amd64
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-amd64,mode=max *.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-amd64,mode=max
arm64_extra_builds: arm64_extra_builds:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
name: ARM Extra Build name: ARM Extra Build
needs: needs:
- arm64_build - arm64_build
@@ -174,8 +178,9 @@ jobs:
with: with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Rockchip build - name: Build and push Rockchip build
uses: docker/bake-action@v3 uses: docker/bake-action@v6
with: with:
source: .
push: true push: true
targets: rk targets: rk
files: docker/rockchip/rk.hcl files: docker/rockchip/rk.hcl
@@ -183,7 +188,7 @@ jobs:
rk.tags=${{ steps.setup.outputs.image-name }}-rk rk.tags=${{ steps.setup.outputs.image-name }}-rk
*.cache-from=type=gha *.cache-from=type=gha
combined_extra_builds: combined_extra_builds:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
name: Combined Extra Builds name: Combined Extra Builds
needs: needs:
- amd64_build - amd64_build
@@ -199,8 +204,9 @@ jobs:
with: with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Hailo-8l build - name: Build and push Hailo-8l build
uses: docker/bake-action@v4 uses: docker/bake-action@v6
with: with:
source: .
push: true push: true
targets: h8l targets: h8l
files: docker/hailo8l/h8l.hcl files: docker/hailo8l/h8l.hcl
@@ -212,8 +218,9 @@ jobs:
env: env:
AMDGPU: gfx AMDGPU: gfx
HSA_OVERRIDE: 0 HSA_OVERRIDE: 0
uses: docker/bake-action@v3 uses: docker/bake-action@v6
with: with:
source: .
push: true push: true
targets: rocm targets: rocm
files: docker/rocm/rocm.hcl files: docker/rocm/rocm.hcl
@@ -223,7 +230,7 @@ jobs:
# The majority of users running arm64 are rpi users, so the rpi # The majority of users running arm64 are rpi users, so the rpi
# build should be the primary arm64 image # build should be the primary arm64 image
assemble_default_build: assemble_default_build:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
name: Assemble and push default build name: Assemble and push default build
needs: needs:
- amd64_build - amd64_build
+6
View File
@@ -116,6 +116,12 @@ genai:
model: gpt-4o model: gpt-4o
``` ```
::: note
To use a different OpenAI-compatible API endpoint, set the `OPENAI_BASE_URL` environment variable to your provider's API URL.
:::
## Azure OpenAI ## Azure OpenAI
Microsoft offers several vision models through Azure OpenAI. A subscription is required. Microsoft offers several vision models through Azure OpenAI. A subscription is required.
+1 -1
View File
@@ -7,7 +7,7 @@ title: 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: 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 - WebRTC or MSE for live viewing with audio, higher resolutions and frame rates than the jsmpeg stream which is limited to the detect stream and does not support audio
- Live stream support for cameras in Home Assistant Integration - Live stream support for cameras in Home Assistant Integration
- RTSP relay for use with other consumers to reduce the number of connections to your camera streams - RTSP relay for use with other consumers to reduce the number of connections to your camera streams
+3 -3
View File
@@ -29,7 +29,9 @@ You cannot use the `environment_vars` section of your Frigate configuration file
## Submit examples ## Submit examples
Once your API key is configured, you can submit examples directly from the Explore page in Frigate using the `Frigate+` button. Once your API key is configured, you can submit examples directly from the Explore page in Frigate. From the More Filters menu, select "Has a Snapshot - Yes" and "Submitted to Frigate+ - No", and press Apply at the bottom of the pane. Then, click on a thumbnail and select the Snapshot tab.
You can use your keyboard's left and right arrow keys to quickly navigate between the tracked object snapshots.
:::note :::note
@@ -37,8 +39,6 @@ Snapshots must be enabled to be able to submit examples to Frigate+
::: :::
![Send To Plus](/img/plus/send-to-plus.jpg)
![Submit To Plus](/img/plus/submit-to-plus.jpg) ![Submit To Plus](/img/plus/submit-to-plus.jpg)
### Annotate and verify ### Annotate and verify
+4
View File
@@ -17,6 +17,10 @@ ffmpeg:
record: preset-record-generic-audio-aac record: preset-record-generic-audio-aac
``` ```
### How can I get sound in live view?
Audio is only supported for live view when go2rtc is configured, see [the live docs](../configuration/live.md) for more information.
### I can't view recordings in the Web UI. ### I can't view recordings in the Web UI.
Ensure your cameras send h264 encoded video, or [transcode them](/configuration/restream.md). Ensure your cameras send h264 encoded video, or [transcode them](/configuration/restream.md).
+1
View File
@@ -153,6 +153,7 @@ def config(request: Request):
config["plus"] = {"enabled": request.app.frigate_config.plus_api.is_active()} config["plus"] = {"enabled": request.app.frigate_config.plus_api.is_active()}
config["model"]["colormap"] = config_obj.model.colormap config["model"]["colormap"] = config_obj.model.colormap
config["model"]["all_attributes"] = config_obj.model.all_attributes
# use merged labelamp # use merged labelamp
for detector_config in config["detectors"].values(): for detector_config in config["detectors"].values():
+11 -7
View File
@@ -26,14 +26,13 @@ from frigate.storage import StorageMaintainer
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def check_csrf(request: Request): def check_csrf(request: Request) -> bool:
if request.method in ["GET", "HEAD", "OPTIONS", "TRACE"]: if request.method in ["GET", "HEAD", "OPTIONS", "TRACE"]:
pass return True
if "origin" in request.headers and "x-csrf-token" not in request.headers: if "origin" in request.headers and "x-csrf-token" not in request.headers:
return JSONResponse( return False
content={"success": False, "message": "Missing CSRF header"},
status_code=401, return True
)
# Used to retrieve the remote-user header: https://starlette-context.readthedocs.io/en/latest/plugins.html#easy-mode # Used to retrieve the remote-user header: https://starlette-context.readthedocs.io/en/latest/plugins.html#easy-mode
@@ -71,7 +70,12 @@ def create_fastapi_app(
@app.middleware("http") @app.middleware("http")
async def frigate_middleware(request: Request, call_next): async def frigate_middleware(request: Request, call_next):
# Before request # Before request
check_csrf(request) if not check_csrf(request):
return JSONResponse(
content={"success": False, "message": "Missing CSRF header"},
status_code=401,
)
if database.is_closed(): if database.is_closed():
database.connect() database.connect()
+6 -6
View File
@@ -111,12 +111,12 @@ PRESETS_HW_ACCEL_ENCODE_BIRDSEYE = {
"preset-rpi-64-h265": "{0} -hide_banner {1} -c:v hevc_v4l2m2m {2}", "preset-rpi-64-h265": "{0} -hide_banner {1} -c:v hevc_v4l2m2m {2}",
FFMPEG_HWACCEL_VAAPI: "{0} -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_device {3} {1} -c:v h264_vaapi -g 50 -bf 0 -profile:v high -level:v 4.1 -sei:v 0 -an -vf format=vaapi|nv12,hwupload {2}", FFMPEG_HWACCEL_VAAPI: "{0} -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_device {3} {1} -c:v h264_vaapi -g 50 -bf 0 -profile:v high -level:v 4.1 -sei:v 0 -an -vf format=vaapi|nv12,hwupload {2}",
"preset-intel-qsv-h264": "{0} -hide_banner {1} -c:v h264_qsv -g 50 -bf 0 -profile:v high -level:v 4.1 -async_depth:v 1 {2}", "preset-intel-qsv-h264": "{0} -hide_banner {1} -c:v h264_qsv -g 50 -bf 0 -profile:v high -level:v 4.1 -async_depth:v 1 {2}",
"preset-intel-qsv-h265": "{0} -hide_banner {1} -c:v h264_qsv -g 50 -bf 0 -profile:v high -level:v 4.1 -async_depth:v 1 {2}", "preset-intel-qsv-h265": "{0} -hide_banner {1} -c:v h264_qsv -g 50 -bf 0 -profile:v main -level:v 4.1 -async_depth:v 1 {2}",
FFMPEG_HWACCEL_NVIDIA: "{0} -hide_banner {1} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {2}", FFMPEG_HWACCEL_NVIDIA: "{0} -hide_banner {1} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {2}",
"preset-jetson-h264": "{0} -hide_banner {1} -c:v h264_nvmpi -profile high {2}", "preset-jetson-h264": "{0} -hide_banner {1} -c:v h264_nvmpi -profile high {2}",
"preset-jetson-h265": "{0} -hide_banner {1} -c:v h264_nvmpi -profile high {2}", "preset-jetson-h265": "{0} -hide_banner {1} -c:v h264_nvmpi -profile main {2}",
"preset-rk-h264": "{0} -hide_banner {1} -c:v h264_rkmpp -profile:v high {2}", "preset-rk-h264": "{0} -hide_banner {1} -c:v h264_rkmpp -profile:v high {2}",
"preset-rk-h265": "{0} -hide_banner {1} -c:v hevc_rkmpp -profile:v high {2}", "preset-rk-h265": "{0} -hide_banner {1} -c:v hevc_rkmpp -profile:v main {2}",
"default": "{0} -hide_banner {1} -c:v libx264 -g 50 -profile:v high -level:v 4.1 -preset:v superfast -tune:v zerolatency {2}", "default": "{0} -hide_banner {1} -c:v libx264 -g 50 -profile:v high -level:v 4.1 -preset:v superfast -tune:v zerolatency {2}",
} }
PRESETS_HW_ACCEL_ENCODE_BIRDSEYE["preset-nvidia-h264"] = ( PRESETS_HW_ACCEL_ENCODE_BIRDSEYE["preset-nvidia-h264"] = (
@@ -131,13 +131,13 @@ PRESETS_HW_ACCEL_ENCODE_TIMELAPSE = {
"preset-rpi-64-h265": "{0} -hide_banner {1} -c:v hevc_v4l2m2m -pix_fmt yuv420p {2}", "preset-rpi-64-h265": "{0} -hide_banner {1} -c:v hevc_v4l2m2m -pix_fmt yuv420p {2}",
FFMPEG_HWACCEL_VAAPI: "{0} -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_device {3} {1} -c:v h264_vaapi {2}", FFMPEG_HWACCEL_VAAPI: "{0} -hide_banner -hwaccel vaapi -hwaccel_output_format vaapi -hwaccel_device {3} {1} -c:v h264_vaapi {2}",
"preset-intel-qsv-h264": "{0} -hide_banner {1} -c:v h264_qsv -profile:v high -level:v 4.1 -async_depth:v 1 {2}", "preset-intel-qsv-h264": "{0} -hide_banner {1} -c:v h264_qsv -profile:v high -level:v 4.1 -async_depth:v 1 {2}",
"preset-intel-qsv-h265": "{0} -hide_banner {1} -c:v hevc_qsv -profile:v high -level:v 4.1 -async_depth:v 1 {2}", "preset-intel-qsv-h265": "{0} -hide_banner {1} -c:v hevc_qsv -profile:v main -level:v 4.1 -async_depth:v 1 {2}",
FFMPEG_HWACCEL_NVIDIA: "{0} -hide_banner -hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 8 {1} -c:v h264_nvenc {2}", FFMPEG_HWACCEL_NVIDIA: "{0} -hide_banner -hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 8 {1} -c:v h264_nvenc {2}",
"preset-nvidia-h265": "{0} -hide_banner -hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 8 {1} -c:v hevc_nvenc {2}", "preset-nvidia-h265": "{0} -hide_banner -hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 8 {1} -c:v hevc_nvenc {2}",
"preset-jetson-h264": "{0} -hide_banner {1} -c:v h264_nvmpi -profile high {2}", "preset-jetson-h264": "{0} -hide_banner {1} -c:v h264_nvmpi -profile high {2}",
"preset-jetson-h265": "{0} -hide_banner {1} -c:v hevc_nvmpi -profile high {2}", "preset-jetson-h265": "{0} -hide_banner {1} -c:v hevc_nvmpi -profile main {2}",
"preset-rk-h264": "{0} -hide_banner {1} -c:v h264_rkmpp -profile:v high {2}", "preset-rk-h264": "{0} -hide_banner {1} -c:v h264_rkmpp -profile:v high {2}",
"preset-rk-h265": "{0} -hide_banner {1} -c:v hevc_rkmpp -profile:v high {2}", "preset-rk-h265": "{0} -hide_banner {1} -c:v hevc_rkmpp -profile:v main {2}",
"default": "{0} -hide_banner {1} -c:v libx264 -preset:v ultrafast -tune:v zerolatency {2}", "default": "{0} -hide_banner {1} -c:v libx264 -preset:v ultrafast -tune:v zerolatency {2}",
} }
PRESETS_HW_ACCEL_ENCODE_TIMELAPSE["preset-nvidia-h264"] = ( PRESETS_HW_ACCEL_ENCODE_TIMELAPSE["preset-nvidia-h264"] = (
+3 -3
View File
@@ -411,19 +411,19 @@ class OnvifController:
# The onvif spec says this can report as +INF and -INF, so this may need to be modified # The onvif spec says this can report as +INF and -INF, so this may need to be modified
pan = numpy.interp( pan = numpy.interp(
pan, pan,
[-1, 1],
[ [
self.cams[camera_name]["relative_fov_range"]["XRange"]["Min"], self.cams[camera_name]["relative_fov_range"]["XRange"]["Min"],
self.cams[camera_name]["relative_fov_range"]["XRange"]["Max"], self.cams[camera_name]["relative_fov_range"]["XRange"]["Max"],
], ],
[-1, 1],
) )
tilt = numpy.interp( tilt = numpy.interp(
tilt, tilt,
[-1, 1],
[ [
self.cams[camera_name]["relative_fov_range"]["YRange"]["Min"], self.cams[camera_name]["relative_fov_range"]["YRange"]["Min"],
self.cams[camera_name]["relative_fov_range"]["YRange"]["Max"], self.cams[camera_name]["relative_fov_range"]["YRange"]["Max"],
], ],
[-1, 1],
) )
move_request.Speed = { move_request.Speed = {
@@ -536,11 +536,11 @@ class OnvifController:
# function takes in 0 to 1 for zoom, interpolate to the values of the camera. # function takes in 0 to 1 for zoom, interpolate to the values of the camera.
zoom = numpy.interp( zoom = numpy.interp(
zoom, zoom,
[0, 1],
[ [
self.cams[camera_name]["absolute_zoom_range"]["XRange"]["Min"], self.cams[camera_name]["absolute_zoom_range"]["XRange"]["Min"],
self.cams[camera_name]["absolute_zoom_range"]["XRange"]["Max"], self.cams[camera_name]["absolute_zoom_range"]["XRange"]["Max"],
], ],
[0, 1],
) )
move_request.Speed = {"Zoom": speed} move_request.Speed = {"Zoom": speed}
+10 -3
View File
@@ -121,22 +121,29 @@ class RecordingCleanup(threading.Thread):
review_start = 0 review_start = 0
deleted_recordings = set() deleted_recordings = set()
kept_recordings: list[tuple[float, float]] = [] kept_recordings: list[tuple[float, float]] = []
recording: Recordings
for recording in recordings: for recording in recordings:
keep = False keep = False
mode = None mode = None
# Now look for a reason to keep this recording segment # Now look for a reason to keep this recording segment
for idx in range(review_start, len(reviews)): for idx in range(review_start, len(reviews)):
review: ReviewSegment = reviews[idx] review: ReviewSegment = reviews[idx]
severity = review.severity
pre_capture = config.record.get_review_pre_capture(severity)
post_capture = config.record.get_review_post_capture(severity)
# if the review starts in the future, stop checking reviews # if the review starts in the future, stop checking reviews
# and let this recording segment expire # and let this recording segment expire
if review.start_time > recording.end_time: if review.start_time - pre_capture > recording.end_time:
keep = False keep = False
break break
# if the review is in progress or ends after the recording starts, keep it # if the review is in progress or ends after the recording starts, keep it
# and stop looking at reviews # and stop looking at reviews
if review.end_time is None or review.end_time >= recording.start_time: if (
review.end_time is None
or review.end_time + post_capture >= recording.start_time
):
keep = True keep = True
mode = ( mode = (
config.record.alerts.retain.mode config.record.alerts.retain.mode
@@ -149,7 +156,7 @@ class RecordingCleanup(threading.Thread):
# this review and check the next review for an overlap. # this review and check the next review for an overlap.
# since the review and recordings are sorted, we can skip review # since the review and recordings are sorted, we can skip review
# that end before the previous recording segment started on future segments # that end before the previous recording segment started on future segments
if review.end_time < recording.start_time: if review.end_time + post_capture < recording.start_time:
review_start = idx review_start = idx
# Delete recordings outside of the retention window or based on the retention mode # Delete recordings outside of the retention window or based on the retention mode
+12 -1
View File
@@ -1,4 +1,4 @@
import { useMemo } from "react"; import { useCallback, useMemo } from "react";
import { useApiHost } from "@/api"; import { useApiHost } from "@/api";
import { getIconForLabel } from "@/utils/iconUtil"; import { getIconForLabel } from "@/utils/iconUtil";
import useSWR from "swr"; import useSWR from "swr";
@@ -33,6 +33,16 @@ export default function SearchThumbnail({
onClick(searchResult, true, false); onClick(searchResult, true, false);
}); });
const handleOnClick = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
if (e.metaKey) {
e.stopPropagation();
onClick(searchResult, true, false);
}
},
[searchResult, onClick],
);
const objectLabel = useMemo(() => { const objectLabel = useMemo(() => {
if ( if (
!config || !config ||
@@ -57,6 +67,7 @@ export default function SearchThumbnail({
<div className={`size-full ${imgLoaded ? "visible" : "invisible"}`}> <div className={`size-full ${imgLoaded ? "visible" : "invisible"}`}>
<img <img
ref={imgRef} ref={imgRef}
onClick={handleOnClick}
className={cn( className={cn(
"size-full select-none object-cover object-center opacity-100 transition-opacity", "size-full select-none object-cover object-center opacity-100 transition-opacity",
)} )}
@@ -61,7 +61,9 @@ export default function SearchFilterGroup({
} }
const cameraConfig = config.cameras[camera]; const cameraConfig = config.cameras[camera];
cameraConfig.objects.track.forEach((label) => { cameraConfig.objects.track.forEach((label) => {
labels.add(label); if (!config.model.all_attributes.includes(label)) {
labels.add(label);
}
}); });
if (cameraConfig.audio.enabled_in_config) { if (cameraConfig.audio.enabled_in_config) {
@@ -85,6 +85,7 @@ type SearchDetailDialogProps = {
setSearch: (search: SearchResult | undefined) => void; setSearch: (search: SearchResult | undefined) => void;
setSearchPage: (page: SearchTab) => void; setSearchPage: (page: SearchTab) => void;
setSimilarity?: () => void; setSimilarity?: () => void;
setInputFocused: React.Dispatch<React.SetStateAction<boolean>>;
}; };
export default function SearchDetailDialog({ export default function SearchDetailDialog({
search, search,
@@ -92,6 +93,7 @@ export default function SearchDetailDialog({
setSearch, setSearch,
setSearchPage, setSearchPage,
setSimilarity, setSimilarity,
setInputFocused,
}: SearchDetailDialogProps) { }: SearchDetailDialogProps) {
const { data: config } = useSWR<FrigateConfig>("config", { const { data: config } = useSWR<FrigateConfig>("config", {
revalidateOnFocus: false, revalidateOnFocus: false,
@@ -232,6 +234,7 @@ export default function SearchDetailDialog({
config={config} config={config}
setSearch={setSearch} setSearch={setSearch}
setSimilarity={setSimilarity} setSimilarity={setSimilarity}
setInputFocused={setInputFocused}
/> />
)} )}
{page == "snapshot" && ( {page == "snapshot" && (
@@ -266,12 +269,14 @@ type ObjectDetailsTabProps = {
config?: FrigateConfig; config?: FrigateConfig;
setSearch: (search: SearchResult | undefined) => void; setSearch: (search: SearchResult | undefined) => void;
setSimilarity?: () => void; setSimilarity?: () => void;
setInputFocused: React.Dispatch<React.SetStateAction<boolean>>;
}; };
function ObjectDetailsTab({ function ObjectDetailsTab({
search, search,
config, config,
setSearch, setSearch,
setSimilarity, setSimilarity,
setInputFocused,
}: ObjectDetailsTabProps) { }: ObjectDetailsTabProps) {
const apiHost = useApiHost(); const apiHost = useApiHost();
@@ -283,6 +288,14 @@ function ObjectDetailsTab({
const [desc, setDesc] = useState(search?.data.description); const [desc, setDesc] = useState(search?.data.description);
const handleDescriptionFocus = useCallback(() => {
setInputFocused(true);
}, [setInputFocused]);
const handleDescriptionBlur = useCallback(() => {
setInputFocused(false);
}, [setInputFocused]);
// we have to make sure the current selected search item stays in sync // we have to make sure the current selected search item stays in sync
useEffect(() => setDesc(search?.data.description ?? ""), [search]); useEffect(() => setDesc(search?.data.description ?? ""), [search]);
@@ -499,6 +512,8 @@ function ObjectDetailsTab({
placeholder="Description of the tracked object" placeholder="Description of the tracked object"
value={desc} value={desc}
onChange={(e) => setDesc(e.target.value)} onChange={(e) => setDesc(e.target.value)}
onFocus={handleDescriptionFocus}
onBlur={handleDescriptionBlur}
/> />
</> </>
)} )}
+1
View File
@@ -343,6 +343,7 @@ export interface FrigateConfig {
width: number; width: number;
colormap: { [key: string]: [number, number, number] }; colormap: { [key: string]: [number, number, number] };
attributes_map: { [key: string]: [string] }; attributes_map: { [key: string]: [string] };
all_attributes: [string];
}; };
motion: Record<string, unknown> | null; motion: Record<string, unknown> | null;
+1
View File
@@ -444,6 +444,7 @@ export default function SearchView({
setSimilarity={ setSimilarity={
searchDetail && (() => setSimilaritySearch(searchDetail)) searchDetail && (() => setSimilaritySearch(searchDetail))
} }
setInputFocused={setInputFocused}
/> />
<div <div