* Bump protobuf
Bumps the pip-trt-security group with 1 update in the /docker/tensorrt directory: [protobuf](https://developers.google.com/protocol-buffers/).
Updates `protobuf` from 3.20.3 to 5.29.6
---
updated-dependencies:
- dependency-name: protobuf
dependency-version: 5.29.6
dependency-type: direct:production
dependency-group: pip-trt-security
...
Signed-off-by: dependabot[bot] <support@github.com>
* keep amd64 tensorrt protobuf on 7.36
Dependabot moved every protobuf pin in docker/tensorrt to 5.29, but the security alerts only cover the aarch64 pin. On amd64 that would downgrade protobuf below what TensorFlow 2.21 requires (>=6.31.1), and the image would build with a pip warning and fail at import tensorflow. This keeps the aarch64 change to 5.29.6, which is the same change verified on #24350 (onnx 1.14.0 and TensorFlow 2.19 both work on it).
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
* keep base path in logout redirect
* store manual event sub labels as verified objects
Manual events created with a sub label were stored in the review item as "label: sub_label", so review previews and collapsed History cards showed a generic icon, the review label filter missed them, and the sub label never reached sub_labels for chips, notifications, or GenAI prompts. The object processor now publishes the label and sub label separately, and the review maintainer stores them as label-verified plus a sub_labels entry, the same as tracked objects.
* only expire snapshots for events that still have one
Snapshot expiry selected every event older than snapshot retention, including events whose snapshot was already deleted, so with snapshot retention shorter than alert/detection retention the same missing files were unlinked again every cleanup cycle until the event row expired. That was nearly free on local disk but caused constant metadata load on network shares. The expiry queries now filter on has_snapshot so each snapshot is deleted once.
* Fix audio worker startup after enabling a disabled camera
* Document audio lifecycle test helpers
* Focus audio startup coverage on the enabled update regression
* set modified time on case download zip entries
Case download zip entries were written with the zip format's 1980 placeholder date, so extracted files showed a blank or 1980 modified date. Each entry now uses the export's recording start time, converted with the UI timezone since zip times have no zone and extractors read them as local time.
* clamp case zip entry dates to 1980
* skip vod clips that start after their audio ends
When a camera's audio dropped out, a recording segment could end its audio early. If a playback range started past that point in the segment, nginx-vod saw no audio track in the first clip, failed the whole range with a track count mismatch, and left the player spinning for up to an hour of footage. Recording now stores each segment's audio length alongside its keyframe index, from the same ffprobe pass, and plan_clip skips a clip whose window starts at or after its audio ends when the range has other clips. A range made of only that clip still plays video-only, and legacy rows without the new column are unchanged.
* keep audio skip consistent with playback
Clips were skipped even when the range would be served video-only, and seeking into a skipped span landed on later footage. The skip now only applies when every row in the range carries audio, and seeks inside a zero-duration span land on the next clip.
* show validation errors in multi-camera export
Clearing part of a time field in the export time picker set the range to NaN, while the picker kept showing a valid fallback time. The multi-camera Export button then stayed disabled with no explanation, and non-admins could never start an export because a case name was required even though they can't see the case field. The picker now ignores incomplete time values. The multi-camera tab shows inline errors for an invalid range, no selected cameras, and a blank case name, only requires a case name from admins, and prefills a default case name.
* add viewer export test and remove stale non-admin case text
* fix test
* fix races in ui settings import e2e tests
confirmImport clicked any button named "Import", so when the file parse was slow it hit the Import Settings button before the dialog opened and the page never reloaded. The click is now scoped to the dialog. The legacy layouts test also wrote naturalAspectLayout after the view had already read it, so it reloads the view after the write.
* update zod to 4.6.7
zod 4 types the input of z.coerce.number() as unknown, so the zone, camera wizard, and classification edit forms pass number to keep their field types. The ONVIF port error showed zod's built-in English text, which zod 4 rewords, so it now uses a translated message. New e2e tests cover zone inertia validation and camera wizard step 1 validation.
* stagger the daily region grid rebuild across cameras
Every camera requested its region grid at exactly 02:00, so the requests queued up in the dispatcher and held up recording segment inserts behind them. Each camera now picks a random offset of up to an hour.
* only log ffmpeg crashes when restarting
* round preview skeleton corners to match the player
The cover image only checked the first category returned by the dataset
API (after skipping none/unknown) and gave up if it had no images. Since
category order follows the dataset directory listing, a model whose first
listed category is still empty showed the skeleton placeholder even when
other categories had plenty of images.
Use the first category that actually has images instead.
* Let the zmq detector skip the input transform when the detector applies it
_transform_input converts the frame to the model's layout and dtype before
the detector sees it. For a remote detector that can apply the transform
itself, often on a GPU, that work is done twice over and the tensor is sent
in its most expensive form.
The detector now reports input_transform_offload in the model handshake. When
it does, Frigate sends the frame as captured (uint8 NHWC) and adds the model's
expected input_tensor and input_dtype to the request header, so the detector
can apply them. The model config is unchanged either way: the expected format
is relayed, not overridden.
The choice is read per frame rather than cached, because the detector redoes
its handshake on a timeout or socket error, so restarting or upgrading it
while Frigate runs already works and keeps working here.
Negotiated rather than configured, for two reasons. The detector is what knows
whether it can apply the transform, and a user who merely believed it could
would send unconverted frames to a model that cannot take them. It also needs
no config surface, which the device string format has no room for.
On a 640x640 region, measured on a Ryzen 9 5900XT, the host-side cost of
handing the detector a frame drops from about 4 ms to under 0.1 ms per
detection, and the payload from 4.9 MB to 1.2 MB. Nearly all of it is
tobytes(): np.transpose returns a view and astype keeps its strides by
default, so the transpose is deferred to serialization, where it becomes a
strided gather over the whole float32 tensor. The YUV to RGB conversion is
unaffected and stays on the host.
* Test that the handshake decides whether the input transform is applied
Parameterized over the three handshake values against the header the detector
ends up sending: reported, the frame goes over as captured uint8 NHWC with the
model's input_tensor and input_dtype declared; not reported or absent, the
existing transform runs and those fields are omitted.
A second case drives a real reconnect -- a timed out request, then a handshake
from an upgraded detector -- and checks the next frame follows the new answer
rather than the one from startup.
---------
Co-authored-by: Eric <eric@localhost>
* keep post capture segments after a review item ends
The recording maintainer only loaded review items whose end_time was after the oldest cached segment, so once an item closed in the database the segments in its post_capture window no longer matched it. With continuous recording off and no motion after the item, everything past roughly the review cutoff_time was deleted from the cache instead of being kept for the full post_capture. The query bound now subtracts the camera's largest post_capture, and the per-item check still decides the exact cutoff.
* fix mypy and add docstring
Recording retention around review items applied only the first review item, in start order, whose pre and post capture window covered a segment. When an alert and a detection were close together, a quiet segment covered by both was dropped by the detection's motion mode without checking the alert's all mode. The loop also stopped at a review item that started too late to reach the segment, even when a later alert with a longer pre_capture did reach it. Both the recording maintainer and cleanup now check every review item whose window covers the segment and keep it if any of their retain modes would.
* materialize event counts in metrics endpoint
The metrics endpoint stored a lazy peewee query on the shared Prometheus collector, and collect() ran it later. Overlapping scrapes run in separate threadpool threads, so two requests could execute and step the same query's sqlite cursor at once. The rows are now read with list() before they reach the collector.
* fix fps overflow restarting ffmpeg after 3 seconds instead of 3 retry intervals
This is a regression from 0.18. The watchdog has restarted ffmpeg after 3 consecutive fps overflow checks since #13651, which was 3 retry intervals (30s by default) when the loop ran every retry_interval. #21297 moved the loop to 1s ticks without converting the count, so the window shrank to 3s. camera_fps is a 10s average, so one burst of buffered frames after a stream hiccup could keep it over the limit long enough to restart a healthy camera, and the next reconnect burst could repeat it. The threshold is now 3 * retry_interval ticks. When the count filled while restarts were still rate limited, the watchdog logged "Exiting ffmpeg...", marked the camera offline and reset the count without restarting. The check now waits for can_restart, so the log, status and restart always happen together.
* keep recording cleanup running after a failed pass
An exception anywhere in a cleanup pass, such as an OSError from a read-only or unmounted storage path, ended the recording cleanup thread with nothing logged. Retention stopped until restart, and recordings, previews and database rows kept growing. Each pass now logs the exception and the loop continues.
* expose frigate_camera_events as a gauge
The metric reports the current count of events in the database, which drops when retention prunes old events. As a counter, each prune read as a counter reset, so increase() and rate() inflated event counts. The series name changes from frigate_camera_events_total to frigate_camera_events. The test's porch/inside assertion was missing its `in event.text` and always passed.
* keep uvicorn idle connections open longer than nginx's upstream keepalive
uvicorn closed idle connections after its 5s default while nginx kept them pooled for 60s, so a request nginx sent on a connection uvicorn was closing got a 502. nginx retries that on a fresh connection for most methods, but not for POST or PATCH.
* snapshot websocket clients under the manager lock in the output loop
The output loop iterated ws4py's live websocket dict without its lock, so a client connecting or disconnecting mid-iteration raised "dictionary changed size during iteration" and killed the output loop. It now copies the clients under manager.lock once per frame and skips terminated ones, matching the jsmpeg and birdseye broadcasters.
* only cache preview frames when recording is enabled
The segment-end check tested the record config object instead of record.enabled, and the first frame after startup was written unconditionally. With recording disabled, the first segment still cached frames and encoded a preview.
* fix untranslated unknown resolution text in camera info dialog
* detect all AMD GPUs from vainfo
The vainfo check only matched "AMD Radeon Graphics", so drivers reporting names like "AMD Radeon Vega 8 Graphics", "AMD Radeon RX 6600", "AMD Custom GPU 0405", or older codename strings fell through and their GPU stats were collected as Intel. It now matches Mesa's "for AMD" driver string.
* return a 500 instead of raising when go2rtc is unreachable
The two go2rtc stream GETs had no exception handling or timeout, so a go2rtc outage raised a connection error with a traceback, and a hung go2rtc held the request open indefinitely. Both now catch request errors, log the cause, and use a timeout. The per-stream probe gets a 35s read timeout because go2rtc probes each source in turn, and one unresponsive source takes about 10s.
* retry camera snapshots after a failed load
AutoUpdatingCameraImage only scheduled the next frame from onLoad, so a single failed latest.webp request froze the image until the component remounted. It now retries on error, dropping the periodic cache key so the retry requests a fresh URL.
* reuse pooled upstream connections for nginx auth subrequests
The /auth subrequest proxied to a variable address, which nginx can't pool, and its location-level headers dropped the inherited Connection header so nginx sent Connection: close. Every authenticated request opened and closed its own loopback connection to the API. It now proxies to the frigate_api upstream with an empty Connection header so the keepalive pool is reused.