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
Nicolas MowenandGitHub 83ac42cbdc Use correct path for script (#16045) 2025-01-18 21:33:13 -07:00
Nicolas MowenandGitHub a5ce8d0d77 Fix env variable exporting (#16043) 2025-01-18 21:31:56 -06:00
Nicolas MowenandGitHub 0ee2e404da Correctly calculate ffmpeg version based on ffmpeg path (#16041)
* Correctly calculate ffmpeg version based on ffmpeg path

* Formatting
2025-01-18 20:30:35 -06:00
Marc AltmannandGitHub 3947e79086 update FFmpeg to ensure compatibility with newer kernels (#16027) 2025-01-18 05:48:28 -07:00
Nicolas MowenandGitHub 91ab1071d2 Update docs to make note of go2rtc port requirement (#16013) 2025-01-16 16:14:40 -07:00
Nicolas MowenandGitHub 409e911752 Update integration docs (#15967) 2025-01-13 08:50:44 -06:00
tpjanssenandGitHub 9983bd8d92 Fix API latest image quality and API MIME types (#15964)
* Fix API latest image quality

* Fix mime types

* Code formatting + media_type fix
2025-01-13 07:46:46 -06:00
Nicolas MowenandGitHub 32c71c4108 Clean up handling of ffmpeg specific params (#15956) 2025-01-12 17:47:24 -06:00
Josh HawkinsandGitHub ef6952e3ea Fix display of save button in tracked object details pane (#15946) 2025-01-11 15:23:52 -06:00
25 changed files with 221 additions and 87 deletions
+2 -2
View File
@@ -33,9 +33,9 @@ runs:
with:
string: ${{ github.repository }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Log in to the Container registry
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc
with:
+22 -15
View File
@@ -19,7 +19,7 @@ env:
jobs:
amd64_build:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
name: AMD64 Build
steps:
- name: Check out code
@@ -42,7 +42,7 @@ jobs:
tags: ${{ steps.setup.outputs.image-name }}-amd64
cache-from: type=registry,ref=${{ steps.setup.outputs.cache-name }}-amd64
arm64_build:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
name: ARM Build
steps:
- name: Check out code
@@ -66,8 +66,9 @@ jobs:
${{ steps.setup.outputs.image-name }}-standard-arm64
cache-from: type=registry,ref=${{ steps.setup.outputs.cache-name }}-arm64
- name: Build and push RPi build
uses: docker/bake-action@v4
uses: docker/bake-action@v6
with:
source: .
push: true
targets: rpi
files: docker/rpi/rpi.hcl
@@ -76,7 +77,7 @@ jobs:
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-arm64
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-arm64,mode=max
jetson_jp4_build:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
name: Jetson Jetpack 4
steps:
- name: Check out code
@@ -94,8 +95,9 @@ jobs:
BASE_IMAGE: timongentzsch/l4t-ubuntu20-opencv:latest
SLIM_BASE: timongentzsch/l4t-ubuntu20-opencv:latest
TRT_BASE: timongentzsch/l4t-ubuntu20-opencv:latest
uses: docker/bake-action@v4
uses: docker/bake-action@v6
with:
source: .
push: true
targets: tensorrt
files: docker/tensorrt/trt.hcl
@@ -104,7 +106,7 @@ jobs:
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp4
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp4,mode=max
jetson_jp5_build:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
name: Jetson Jetpack 5
steps:
- name: Check out code
@@ -122,8 +124,9 @@ jobs:
BASE_IMAGE: 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
uses: docker/bake-action@v4
uses: docker/bake-action@v6
with:
source: .
push: true
targets: tensorrt
files: docker/tensorrt/trt.hcl
@@ -132,7 +135,7 @@ jobs:
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp5
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-jp5,mode=max
amd64_extra_builds:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
name: AMD64 Extra Build
needs:
- amd64_build
@@ -149,8 +152,9 @@ jobs:
- name: Build and push TensorRT (x86 GPU)
env:
COMPUTE_LEVEL: "50 60 70 80 90"
uses: docker/bake-action@v4
uses: docker/bake-action@v6
with:
source: .
push: true
targets: tensorrt
files: docker/tensorrt/trt.hcl
@@ -159,7 +163,7 @@ jobs:
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-amd64
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-amd64,mode=max
arm64_extra_builds:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
name: ARM Extra Build
needs:
- arm64_build
@@ -174,8 +178,9 @@ jobs:
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Rockchip build
uses: docker/bake-action@v3
uses: docker/bake-action@v6
with:
source: .
push: true
targets: rk
files: docker/rockchip/rk.hcl
@@ -183,7 +188,7 @@ jobs:
rk.tags=${{ steps.setup.outputs.image-name }}-rk
*.cache-from=type=gha
combined_extra_builds:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
name: Combined Extra Builds
needs:
- amd64_build
@@ -199,8 +204,9 @@ jobs:
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Hailo-8l build
uses: docker/bake-action@v4
uses: docker/bake-action@v6
with:
source: .
push: true
targets: h8l
files: docker/hailo8l/h8l.hcl
@@ -212,8 +218,9 @@ jobs:
env:
AMDGPU: gfx
HSA_OVERRIDE: 0
uses: docker/bake-action@v3
uses: docker/bake-action@v6
with:
source: .
push: true
targets: rocm
files: docker/rocm/rocm.hcl
@@ -223,7 +230,7 @@ jobs:
# The majority of users running arm64 are rpi users, so the rpi
# build should be the primary arm64 image
assemble_default_build:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
name: Assemble and push default build
needs:
- amd64_build
-1
View File
@@ -215,7 +215,6 @@ ENV TRANSFORMERS_NO_ADVISORY_WARNINGS=1
ENV OPENCV_FFMPEG_LOGLEVEL=8
ENV PATH="/usr/local/go2rtc/bin:/usr/local/tempio/bin:/usr/local/nginx/sbin:${PATH}"
ENV LIBAVFORMAT_VERSION_MAJOR=60
# Install dependencies
RUN --mount=type=bind,source=docker/main/install_deps.sh,target=/deps/install_deps.sh \
@@ -42,8 +42,14 @@ function migrate_db_path() {
fi
}
function set_libva_version() {
local ffmpeg_path=$(python3 /usr/local/ffmpeg/get_ffmpeg_path.py)
export LIBAVFORMAT_VERSION_MAJOR=$($ffmpeg_path -version | grep -Po "libavformat\W+\K\d+")
}
echo "[INFO] Preparing Frigate..."
migrate_db_path
set_libva_version
echo "[INFO] Starting Frigate..."
cd /opt/frigate || echo "[ERROR] Failed to change working directory to /opt/frigate"
@@ -43,6 +43,11 @@ function get_ip_and_port_from_supervisor() {
export FRIGATE_GO2RTC_WEBRTC_CANDIDATE_INTERNAL="${ip_address}:${webrtc_port}"
}
function set_libva_version() {
local ffmpeg_path=$(python3 /usr/local/ffmpeg/get_ffmpeg_path.py)
export LIBAVFORMAT_VERSION_MAJOR=$($ffmpeg_path -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
@@ -61,6 +66,8 @@ 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
set_libva_version
readonly config_path="/config"
if [[ -x "${config_path}/go2rtc" ]]; then
@@ -0,0 +1,45 @@
import json
import os
import shutil
import sys
from ruamel.yaml import YAML
sys.path.insert(0, "/opt/frigate")
from frigate.const import (
DEFAULT_FFMPEG_VERSION,
INCLUDED_FFMPEG_VERSIONS,
)
sys.path.remove("/opt/frigate")
yaml = YAML()
config_file = os.environ.get("CONFIG_FILE", "/config/config.yml")
# Check if we can use .yaml instead of .yml
config_file_yaml = config_file.replace(".yml", ".yaml")
if os.path.isfile(config_file_yaml):
config_file = config_file_yaml
try:
with open(config_file) as f:
raw_config = f.read()
if config_file.endswith((".yaml", ".yml")):
config: dict[str, any] = yaml.load(raw_config)
elif config_file.endswith(".json"):
config: dict[str, any] = json.loads(raw_config)
except FileNotFoundError:
config: dict[str, any] = {}
path = config.get("ffmpeg", {}).get("path", "default")
if path == "default":
if shutil.which("ffmpeg") is None:
print(f"/usr/lib/ffmpeg/{DEFAULT_FFMPEG_VERSION}/bin/ffmpeg")
else:
print("ffmpeg")
elif path in INCLUDED_FFMPEG_VERSIONS:
print(f"/usr/lib/ffmpeg/{path}/bin/ffmpeg")
else:
print(f"{path}/bin/ffmpeg")
+2 -2
View File
@@ -22,6 +22,6 @@ ADD https://github.com/MarcA711/rknn-toolkit2/releases/download/v2.0.0/librknnrt
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.1-5/ffmpeg /usr/lib/ffmpeg/6.0/bin/
ADD --chmod=111 https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/download/6.1-5/ffprobe /usr/lib/ffmpeg/6.0/bin/
ADD --chmod=111 https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/download/6.1-7/ffmpeg /usr/lib/ffmpeg/6.0/bin/
ADD --chmod=111 https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/download/6.1-7/ffprobe /usr/lib/ffmpeg/6.0/bin/
ENV PATH="/usr/lib/ffmpeg/6.0/bin/:${PATH}"
-2
View File
@@ -12,7 +12,5 @@ RUN rm -rf /usr/lib/btbn-ffmpeg/
RUN --mount=type=bind,source=docker/rpi/install_deps.sh,target=/deps/install_deps.sh \
/deps/install_deps.sh
ENV LIBAVFORMAT_VERSION_MAJOR=58
WORKDIR /opt/frigate/
COPY --from=rootfs / /
+6
View File
@@ -116,6 +116,12 @@ genai:
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
Microsoft offers several vision models through Azure OpenAI. A subscription is required.
+2
View File
@@ -548,6 +548,8 @@ genai:
# Optional: Restream configuration
# Uses https://github.com/AlexxIT/go2rtc (v1.9.2)
# NOTE: The default go2rtc API port (1984) must be used,
# changing this port for the integrated go2rtc instance is not supported.
go2rtc:
# Optional: Live stream configuration for WebUI.
+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:
- 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
- RTSP relay for use with other consumers to reduce the number of connections to your camera streams
+26 -7
View File
@@ -47,7 +47,7 @@ that card.
## Configuration
When configuring the integration, you will be asked for the `URL` of your Frigate instance which needs to be pointed at the internal unauthenticated port (`5000`) for your instance. This may look like `http://<host>:5000/`.
When configuring the integration, you will be asked for the `URL` of your Frigate instance which can be pointed at the internal unauthenticated port (`5000`) or the authenticated port (`8971`) for your instance. This may look like `http://<host>:5000/`.
### Docker Compose Examples
@@ -55,7 +55,7 @@ If you are running Home Assistant Core and Frigate with Docker Compose on the sa
#### Home Assistant running with host networking
It is not recommended to run Frigate in host networking mode. In this example, you would use `http://172.17.0.1:5000` when configuring the integration.
It is not recommended to run Frigate in host networking mode. In this example, you would use `http://172.17.0.1:5000` or `http://172.17.0.1:8971` when configuring the integration.
```yaml
services:
@@ -75,7 +75,7 @@ services:
#### Home Assistant _not_ running with host networking or in a separate compose file
In this example, you would use `http://frigate:5000` when configuring the integration. There is no need to map the port for the Frigate container.
In this example, it is recommended to connect to the authenticated port, for example, `http://frigate:8971` when configuring the integration. There is no need to map the port for the Frigate container.
```yaml
services:
@@ -103,14 +103,15 @@ If you are using HassOS with the addon, the URL should be one of the following d
| Frigate NVR (Full Access) | `http://ccab4aaf-frigate-fa:5000` |
| Frigate NVR Beta | `http://ccab4aaf-frigate-beta:5000` |
| Frigate NVR Beta (Full Access) | `http://ccab4aaf-frigate-fa-beta:5000` |
| Frigate NVR HailoRT Beta | `http://ccab4aaf-frigate-hailo-beta:5000` |
### Frigate running on a separate machine
If you run Frigate on a separate device within your local network, Home Assistant will need access to port 5000.
If you run Frigate on a separate device within your local network, Home Assistant will need access to port 8971.
#### Local network
Use `http://<frigate_device_ip>:5000` as the URL for the integration. If you want to protect access to port 5000, you can use firewall rules to limit access to the device running Home Assistant.
Use `http://<frigate_device_ip>:8971` as the URL for the integration so that authentication is required.
```yaml
services:
@@ -118,7 +119,7 @@ services:
image: ghcr.io/blakeblackshear/frigate:stable
...
ports:
- "5000:5000"
- "8971:8971"
...
```
@@ -195,12 +196,30 @@ To load a snapshot for a tracked object:
https://HA_URL/api/frigate/notifications/<event-id>/snapshot.jpg
```
To load a video clip of a tracked object:
To load a video clip of a tracked object using an Android device:
```
https://HA_URL/api/frigate/notifications/<event-id>/clip.mp4
```
To load a video clip of a tracked object using an iOS device:
```
https://HA_URL/api/frigate/notifications/<event-id>/master.m3u8
```
To load a preview gif of a tracked object:
```
https://HA_URL/api/frigate/notifications/<event-id>/event_preview.gif
```
To load a preview gif of a review item:
```
https://HA_URL/api/frigate/notifications/<review-id>/review_preview.gif
```
<a name="streams"></a>
## RTSP stream
+3 -3
View File
@@ -29,7 +29,9 @@ You cannot use the `environment_vars` section of your Frigate configuration file
## 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
@@ -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)
### Annotate and verify
+4
View File
@@ -17,6 +17,10 @@ ffmpeg:
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.
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["model"]["colormap"] = config_obj.model.colormap
config["model"]["all_attributes"] = config_obj.model.all_attributes
# use merged labelamp
for detector_config in config["detectors"].values():
+11 -7
View File
@@ -26,14 +26,13 @@ from frigate.storage import StorageMaintainer
logger = logging.getLogger(__name__)
def check_csrf(request: Request):
def check_csrf(request: Request) -> bool:
if request.method in ["GET", "HEAD", "OPTIONS", "TRACE"]:
pass
return True
if "origin" in request.headers and "x-csrf-token" not in request.headers:
return JSONResponse(
content={"success": False, "message": "Missing CSRF header"},
status_code=401,
)
return False
return True
# 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")
async def frigate_middleware(request: Request, call_next):
# 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():
database.connect()
+17 -11
View File
@@ -133,6 +133,15 @@ def latest_frame(
"regions": params.regions,
}
quality = params.quality
mime_type = extension
if extension == "png":
quality_params = None
elif extension == "webp":
quality_params = [int(cv2.IMWRITE_WEBP_QUALITY), quality]
else:
quality_params = [int(cv2.IMWRITE_JPEG_QUALITY), quality]
mime_type = "jpeg"
if camera_name in request.app.frigate_config.cameras:
frame = frame_processor.get_current_frame(camera_name, draw_options)
@@ -173,13 +182,11 @@ def latest_frame(
frame = cv2.resize(frame, dsize=(width, height), interpolation=cv2.INTER_AREA)
ret, img = cv2.imencode(
f".{extension}", frame, [int(cv2.IMWRITE_WEBP_QUALITY), quality]
)
ret, img = cv2.imencode(f".{extension}", frame, quality_params)
return Response(
content=img.tobytes(),
media_type=f"image/{extension}",
headers={"Content-Type": f"image/{extension}", "Cache-Control": "no-store"},
media_type=f"image/{mime_type}",
headers={"Content-Type": f"image/{mime_type}", "Cache-Control": "no-store"},
)
elif camera_name == "birdseye" and request.app.frigate_config.birdseye.restream:
frame = cv2.cvtColor(
@@ -192,13 +199,11 @@ def latest_frame(
frame = cv2.resize(frame, dsize=(width, height), interpolation=cv2.INTER_AREA)
ret, img = cv2.imencode(
f".{extension}", frame, [int(cv2.IMWRITE_WEBP_QUALITY), quality]
)
ret, img = cv2.imencode(f".{extension}", frame, quality_params)
return Response(
content=img.tobytes(),
media_type=f"image/{extension}",
headers={"Content-Type": f"image/{extension}", "Cache-Control": "no-store"},
media_type=f"image/{mime_type}",
headers={"Content-Type": f"image/{mime_type}", "Cache-Control": "no-store"},
)
else:
return JSONResponse(
@@ -241,6 +246,7 @@ def get_snapshot_from_recording(
recording: Recordings = recording_query.get()
time_in_segment = frame_time - recording.start_time
codec = "png" if format == "png" else "mjpeg"
mime_type = "png" if format == "png" else "jpeg"
config: FrigateConfig = request.app.frigate_config
image_data = get_image_from_recording(
@@ -257,7 +263,7 @@ def get_snapshot_from_recording(
),
status_code=404,
)
return Response(image_data, headers={"Content-Type": f"image/{format}"})
return Response(image_data, headers={"Content-Type": f"image/{mime_type}"})
except DoesNotExist:
return JSONResponse(
content={
+11 -18
View File
@@ -50,16 +50,9 @@ class LibvaGpuSelector:
return ""
FPS_VFR_PARAM = (
"-fps_mode vfr"
if int(os.getenv("LIBAVFORMAT_VERSION_MAJOR", "59") or "59") >= 59
else "-vsync 2"
)
TIMEOUT_PARAM = (
"-timeout"
if int(os.getenv("LIBAVFORMAT_VERSION_MAJOR", "59") or "59") >= 59
else "-stimeout"
)
LIBAV_VERSION = int(os.getenv("LIBAVFORMAT_VERSION_MAJOR", "59") or "59")
FPS_VFR_PARAM = "-fps_mode vfr" if LIBAV_VERSION >= 59 else "-vsync 2"
TIMEOUT_PARAM = "-timeout" if LIBAV_VERSION >= 59 else "-stimeout"
_gpu_selector = LibvaGpuSelector()
_user_agent_args = [
@@ -71,8 +64,8 @@ PRESETS_HW_ACCEL_DECODE = {
"preset-rpi-64-h264": "-c:v:1 h264_v4l2m2m",
"preset-rpi-64-h265": "-c:v:1 hevc_v4l2m2m",
FFMPEG_HWACCEL_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 -bsf:v dump_extra", # https://trac.ffmpeg.org/ticket/9766#comment:17
"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 -bsf:v dump_extra", # https://trac.ffmpeg.org/ticket/9766#comment:17
"preset-intel-qsv-h264": f"-hwaccel qsv -qsv_device {_gpu_selector.get_selected_gpu()} -hwaccel_output_format qsv -c:v h264_qsv{' -bsf:v dump_extra' if LIBAV_VERSION >= 61 else ''}", # https://trac.ffmpeg.org/ticket/9766#comment:17
"preset-intel-qsv-h265": f"-load_plugin hevc_hw -hwaccel qsv -qsv_device {_gpu_selector.get_selected_gpu()} -hwaccel_output_format qsv{' -bsf:v dump_extra' if LIBAV_VERSION >= 61 else ''}", # https://trac.ffmpeg.org/ticket/9766#comment:17
FFMPEG_HWACCEL_NVIDIA: "-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}",
@@ -118,12 +111,12 @@ PRESETS_HW_ACCEL_ENCODE_BIRDSEYE = {
"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}",
"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}",
"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-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}",
}
PRESETS_HW_ACCEL_ENCODE_BIRDSEYE["preset-nvidia-h264"] = (
@@ -138,13 +131,13 @@ PRESETS_HW_ACCEL_ENCODE_TIMELAPSE = {
"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}",
"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}",
"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-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-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}",
}
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
pan = numpy.interp(
pan,
[-1, 1],
[
self.cams[camera_name]["relative_fov_range"]["XRange"]["Min"],
self.cams[camera_name]["relative_fov_range"]["XRange"]["Max"],
],
[-1, 1],
)
tilt = numpy.interp(
tilt,
[-1, 1],
[
self.cams[camera_name]["relative_fov_range"]["YRange"]["Min"],
self.cams[camera_name]["relative_fov_range"]["YRange"]["Max"],
],
[-1, 1],
)
move_request.Speed = {
@@ -536,11 +536,11 @@ class OnvifController:
# function takes in 0 to 1 for zoom, interpolate to the values of the camera.
zoom = numpy.interp(
zoom,
[0, 1],
[
self.cams[camera_name]["absolute_zoom_range"]["XRange"]["Min"],
self.cams[camera_name]["absolute_zoom_range"]["XRange"]["Max"],
],
[0, 1],
)
move_request.Speed = {"Zoom": speed}
+10 -3
View File
@@ -121,22 +121,29 @@ class RecordingCleanup(threading.Thread):
review_start = 0
deleted_recordings = set()
kept_recordings: list[tuple[float, float]] = []
recording: Recordings
for recording in recordings:
keep = False
mode = None
# Now look for a reason to keep this recording segment
for idx in range(review_start, len(reviews)):
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
# and let this recording segment expire
if review.start_time > recording.end_time:
if review.start_time - pre_capture > recording.end_time:
keep = False
break
# if the review is in progress or ends after the recording starts, keep it
# 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
mode = (
config.record.alerts.retain.mode
@@ -149,7 +156,7 @@ class RecordingCleanup(threading.Thread):
# this review and check the next review for an overlap.
# since the review and recordings are sorted, we can skip review
# 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
# 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 { getIconForLabel } from "@/utils/iconUtil";
import useSWR from "swr";
@@ -33,6 +33,16 @@ export default function SearchThumbnail({
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(() => {
if (
!config ||
@@ -57,6 +67,7 @@ export default function SearchThumbnail({
<div className={`size-full ${imgLoaded ? "visible" : "invisible"}`}>
<img
ref={imgRef}
onClick={handleOnClick}
className={cn(
"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];
cameraConfig.objects.track.forEach((label) => {
labels.add(label);
if (!config.model.all_attributes.includes(label)) {
labels.add(label);
}
});
if (cameraConfig.audio.enabled_in_config) {
@@ -85,6 +85,7 @@ type SearchDetailDialogProps = {
setSearch: (search: SearchResult | undefined) => void;
setSearchPage: (page: SearchTab) => void;
setSimilarity?: () => void;
setInputFocused: React.Dispatch<React.SetStateAction<boolean>>;
};
export default function SearchDetailDialog({
search,
@@ -92,6 +93,7 @@ export default function SearchDetailDialog({
setSearch,
setSearchPage,
setSimilarity,
setInputFocused,
}: SearchDetailDialogProps) {
const { data: config } = useSWR<FrigateConfig>("config", {
revalidateOnFocus: false,
@@ -232,6 +234,7 @@ export default function SearchDetailDialog({
config={config}
setSearch={setSearch}
setSimilarity={setSimilarity}
setInputFocused={setInputFocused}
/>
)}
{page == "snapshot" && (
@@ -266,12 +269,14 @@ type ObjectDetailsTabProps = {
config?: FrigateConfig;
setSearch: (search: SearchResult | undefined) => void;
setSimilarity?: () => void;
setInputFocused: React.Dispatch<React.SetStateAction<boolean>>;
};
function ObjectDetailsTab({
search,
config,
setSearch,
setSimilarity,
setInputFocused,
}: ObjectDetailsTabProps) {
const apiHost = useApiHost();
@@ -283,6 +288,14 @@ function ObjectDetailsTab({
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
useEffect(() => setDesc(search?.data.description ?? ""), [search]);
@@ -499,6 +512,8 @@ function ObjectDetailsTab({
placeholder="Description of the tracked object"
value={desc}
onChange={(e) => setDesc(e.target.value)}
onFocus={handleDescriptionFocus}
onBlur={handleDescriptionBlur}
/>
</>
)}
@@ -543,16 +558,16 @@ function ObjectDetailsTab({
)}
</div>
)}
{(config?.cameras[search.camera].genai.enabled && search.end_time) ||
(!config?.cameras[search.camera].genai.enabled && (
<Button
variant="select"
aria-label="Save"
onClick={updateDescription}
>
Save
</Button>
))}
{((config?.cameras[search.camera].genai.enabled && search.end_time) ||
!config?.cameras[search.camera].genai.enabled) && (
<Button
variant="select"
aria-label="Save"
onClick={updateDescription}
>
Save
</Button>
)}
</div>
</div>
</div>
+1
View File
@@ -343,6 +343,7 @@ export interface FrigateConfig {
width: number;
colormap: { [key: string]: [number, number, number] };
attributes_map: { [key: string]: [string] };
all_attributes: [string];
};
motion: Record<string, unknown> | null;
+1
View File
@@ -444,6 +444,7 @@ export default function SearchView({
setSimilarity={
searchDetail && (() => setSimilaritySearch(searchDetail))
}
setInputFocused={setInputFocused}
/>
<div