Commit Graph

1606 Commits

Author SHA1 Message Date
ibs0d
a74d9e58e9
Revert "fix: prevent video stretching in timeline scrubbing preview" 2026-03-20 21:11:20 +11:00
Claude
90d9c444c8
fix: prevent video stretching in timeline scrubbing preview
- MsePlayer: change default object-fit fallback from fill to contain
  (grid layout keeps fill via --frigate-mse-object-fit:fill CSS variable)
- PreviewPlayer: add object-contain class to video element
- HlsVideoPlayer: add object-contain class to video element

Recordings view and timeline preview now preserve aspect ratio,
while live grid continues to stretch corridor cameras as before.
2026-03-20 09:47:54 +00:00
Claude
8f990817b4
feat: add role-based visibility for camera groups
Add optional `roles` field to camera groups config to control which user
roles can see each group. Groups without roles are visible only to admins.
Admin users always see all groups. Backend filters groups in GET /config
based on remote-role header. Frontend adds roles multiselect in group
editor (admin only).

https://claude.ai/code/session_011sp9kHQfM39JvVxKHFh1Xq
2026-03-20 03:38:12 +00:00
ibs0d
e8b9f50bc9
Merge branch 'blakeblackshear:dev' into dev 2026-03-20 14:02:44 +11:00
Nicolas Mowen
cedcbdba07
Add ability to toggle camera features via API (#22538)
Some checks are pending
CI / ARM Extra Build (push) Blocked by required conditions
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
* Refactor profile to be a generic state setter API

* Add tool to chat

* Cleanup

* Cleanup
2026-03-19 17:39:28 -05:00
Josh Hawkins
a9a2eecebb
add inherit and none to ffmpeg args widget (#22535)
Some checks are pending
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
2026-03-19 13:11:34 -06:00
Josh Hawkins
e2bfa26719
Add go2rtc streams to settings UI (#22531)
* Add go2rtc settings section

- create separate settings section for all go2rtc streams
- extract credentials mask code into util
- create ffmpeg module utility
- i18n

* add camera config updater topic for live section

to support adding go2rtc streams after configuring a new one via the UI

* clean up

* tweak delete button color for consistency

* tweaks
2026-03-19 10:33:42 -06:00
Josh Hawkins
c93dad9bd9
Camera profile support (#22482)
* add CameraProfileConfig model for named config overrides

* add profiles field to CameraConfig

* add active_profile field to FrigateConfig

Runtime-only field excluded from YAML serialization, tracks which
profile is currently active.

* add ProfileManager for profile activation and persistence

Handles snapshotting base configs, applying profile overrides via
deep_merge + apply_section_update, publishing ZMQ updates, and
persisting active profile to /config/.active_profile.

* add profile API endpoints (GET /profiles, GET/PUT /profile)

* add MQTT and dispatcher integration for profiles

- Subscribe to frigate/profile/set MQTT topic
- Publish profile/state and profiles/available on connect
- Add _on_profile_command handler to dispatcher
- Broadcast active profile state on WebSocket connect

* wire ProfileManager into app startup and FastAPI

- Create ProfileManager after dispatcher init
- Restore persisted profile on startup
- Pass dispatcher and profile_manager to FastAPI app

* add tests for invalid profile values and keys

Tests that Pydantic rejects: invalid field values (fps: "not_a_number"),
unknown section keys (ffmpeg in profile), invalid nested values, and
invalid profiles in full config parsing.

* formatting

* fix CameraLiveConfig JSON serialization error on profile activation

refactor _publish_updates to only publish ZMQ updates for
sections that actually changed, not all sections on affected cameras.

* consolidate

* add enabled field to camera profiles for enabling/disabling cameras

* add zones support to camera profiles

* add frontend profile types, color utility, and config save support

* add profile state management and save preview support

* add profileName prop to BaseSection for profile-aware config editing

* add profile section dropdown and wire into camera settings pages

* add per-profile camera enable/disable to Camera Management view

* add profiles summary page with card-based layout and fix backend zone comparison bug

* add active profile badge to settings toolbar

* i18n

* add red dot for any pending changes including profiles

* profile support for mask and zone editor

* fix hidden field validation errors caused by lodash wildcard and schema gaps

lodash unset does not support wildcard (*) segments, so hidden fields like
filters.*.mask were never stripped from form data, leaving null raw_coordinates
that fail RJSF anyOf validation. Add unsetWithWildcard helper and also strip
hidden fields from the JSON schema itself as defense-in-depth.

* add face_recognition and lpr to profile-eligible sections

* move profile dropdown from section panes to settings header

* add profiles enable toggle and improve empty state

* formatting

* tweaks

* tweak colors and switch

* fix profile save diff, masksAndZones delete, and config sync

* ui tweaks

* ensure profile manager gets updated config

* rename profile settings to ui settings

* refactor profilesview and add dots/border colors when overridden

* implement an update_config method for profile manager

* fix mask deletion

* more unique colors

* add top-level profiles config section with friendly names

* implement profile friendly names and improve profile UI

- Add ProfileDefinitionConfig type and profiles field to FrigateConfig
- Use ProfilesApiResponse type with friendly_name support throughout
- Replace Record<string, unknown> with proper JsonObject/JsonValue types
- Add profile creation form matching zone pattern (Zod + NameAndIdFields)
- Add pencil icon for renaming profile friendly names in ProfilesView
- Move Profiles menu item to first under Camera Configuration
- Add activity indicators on save/rename/delete buttons
- Display friendly names in CameraManagementView profile selector
- Fix duplicate colored dots in management profile dropdown
- Fix i18n namespace for overridden base config tooltips
- Move profile override deletion from dropdown trash icon to footer
  button with confirmation dialog, matching Reset to Global pattern
- Remove Add Profile from section header dropdown to prevent saving
  camera overrides before top-level profile definition exists
- Clean up newProfiles state after API profile deletion
- Refresh profiles SWR cache after saving profile definitions

* remove profile badge in settings and add profiles to main menu

* use icon only on mobile

* change color order

* docs

* show activity indicator on trash icon while deleting a profile

* tweak language

* immediately create profiles on backend instead of deferring to Save All

* hide restart-required fields when editing a profile section

fields that require a restart cannot take effect via profile switching,
so they are merged into hiddenFields when profileName is set

* show active profile indicator in desktop status bar

* fix profile config inheritance bug where Pydantic defaults override base values

The /config API was dumping profile overrides with model_dump() which included
all Pydantic defaults. When the frontend merged these over
the camera's base config, explicitly-set base values were
lost. Now profile overrides are re-dumped with exclude_unset=True so only
user-specified fields are returned.

Also fixes the Save All path generating spurious deletion markers for
restart-required fields that are hidden during profile
editing but not excluded from the raw data sanitization in
prepareSectionSavePayload.

* docs tweaks

* docs tweak

* formatting

* formatting

* fix typing

* fix test pollution

test_maintainer was injecting MagicMock() into sys.modules["frigate.config.camera.updater"] at module load time and never restoring it. When the profile tests later imported CameraConfigUpdateEnum and CameraConfigUpdateTopic from that module, they got mock objects instead of the real dataclass/enum, so equality comparisons always failed

* remove

* fix settings showing profile-merged values when editing base config

When a profile is active, the in-memory config contains effective
(profile-merged) values. The settings UI was displaying these merged
values even when the "Base Config" view was selected.

Backend: snapshot pre-profile base configs in ProfileManager and expose
them via a `base_config` key in the /api/config camera response when a
profile is active. The top-level sections continue to reflect the
effective running config.

Frontend: read from `base_config` when available in BaseSection,
useConfigOverride, useAllCameraOverrides, and prepareSectionSavePayload.
Include formData labels in Object/Audio switches widgets so that labels
added only by a profile override remain visible when editing that profile.

* use rasterized_mask as field

makes it easier to exclude from the schema with exclude=True
prevents leaking of the field when using model_dump for profiles

* fix zones

- Fix zone colors not matching across profiles by falling back to base zone color when profile zone data lacks a color field
- Use base_config for base-layer values in masks/zones view so profile-merged values don't pollute the base config editing view
- Handle zones separately in profile manager snapshot/restore since ZoneConfig requires special serialization (color as private attr, contour generation)
- Inherit base zone color and generate contours for profile zone overrides in profile manager

* formatting

* don't require restart for camera enabled change for profiles

* publish camera state when changing profiles

* formatting

* remove available profiles from mqtt

* improve typing
2026-03-19 09:47:57 -05:00
ibs0d
d8ecc1a9cc
Revert "feat: add personal camera groups with per-user visibility" 2026-03-19 20:05:46 +11:00
Claude
3b3a513929
feat: add personal camera groups with per-user visibility
Add optional `users` field to camera groups config allowing groups to be
restricted to specific users. Groups without users remain visible to all
(backward compatible). Admin users always see all groups. Backend filters
groups in GET /config based on authenticated user. Frontend adds a users
multi-select toggle in the group editor (admin only).

https://claude.ai/code/session_01PooiYnugPWqdCYDq4TU7ti
2026-03-19 04:49:47 +00:00
ibs0d
9cb7902a9d
Merge pull request #73 from ibs0d/claude/fix-zoom-statistics-WFvOm
Claude/fix zoom statistics w fv om
2026-03-19 14:57:22 +11:00
Claude
7ee24b7518
fix: motion dot outside zoom transform in LiveCameraView
Same pattern as DraggableGridLayout: render the dot outside
TransformComponent so it doesn't scale with pinch/zoom.
LivePlayer gets showMotionDot={false} to avoid duplicate.

https://claude.ai/code/session_019B4dJXtcxvHn97ZaqHUB62
2026-03-19 03:51:29 +00:00
ibs0d
e4e0ccc27c
Merge branch 'blakeblackshear:dev' into dev 2026-03-19 14:49:40 +11:00
ryzendigo
2ace8d3670
fix: preserve other cameras' volume when adjusting one (#22508)
setVolumeStates was replacing the entire state object instead of
merging, so changing one camera's volume reset all others to default.

Uses the functional update pattern to preserve existing state, matching
how toggleAudio already works.
2026-03-18 09:40:37 -05:00
Claude
5a8fccd00d
Change review default tab from alerts to motion 2026-03-18 09:50:34 +00:00
Claude
f07636e7ec
fix: remove duplicate MdCircle and useCameraActivity imports
Rebase onto dev introduced duplicate imports since dev already had them.

https://claude.ai/code/session_019B4dJXtcxvHn97ZaqHUB62
2026-03-18 09:11:49 +00:00
ibs0d
301dfc185b
Revert "fix: motion dot outside zoom transform, fix activeMotion logic" 2026-03-18 20:08:04 +11:00
Claude
83be2800f9
fix: motion dot outside zoom transform, fix activeMotion logic
Two fixes:
1. useCameraActivity: replace broken ternary priority with OR — "OFF"
   (truthy string) was silently blocking camera_activity.motion fallback.
   Now: motion === true (from camera_activity) OR detectingMotion === "ON".
2. DraggableGridLayout: render CameraMotionDot outside the zoom transform
   div so the dot doesn't scale with camera zoom. LivePlayer gets
   showMotionDot={false} to avoid duplicate rendering.

https://claude.ai/code/session_019B4dJXtcxvHn97ZaqHUB62
2026-03-18 08:32:56 +00:00
ibs0d
fa05857976
Revert "fix: revert CameraMotionDot, restore built-in LivePlayer motion dot" 2026-03-18 19:12:01 +11:00
ibs0d
b1a9833406
Merge pull request #68 from ibs0d/claude/fix-zoom-statistics-WFvOm
fix: revert CameraMotionDot, restore built-in LivePlayer motion dot
2026-03-18 18:44:59 +11:00
Claude
76652861b5
fix: revert CameraMotionDot, restore built-in LivePlayer motion dot
Remove the external CameraMotionDot component and showMotionDot={false}
override. The ws.ts fix (camera_activity -> motion topic sync) ensures
useCameraActivity gets fresh data, so the built-in dot in LivePlayer works.

https://claude.ai/code/session_019B4dJXtcxvHn97ZaqHUB62
2026-03-18 07:39:00 +00:00
ibs0d
44feb916c2
Merge pull request #66 from ibs0d/claude/fix-zoom-statistics-WFvOm
fix: sync {camera}/motion topic from camera_activity updates
2026-03-18 17:51:56 +11:00
Claude
703ffcf82e
fix: sync {camera}/motion topic from camera_activity updates
applyCameraActivity expanded camera_activity into many per-camera
topics ({camera}/enabled/state, {camera}/detect/state, etc.) but
never synced the motion field into {camera}/motion.

This caused a critical bug: a stale retained MQTT "OFF" value in
{camera}/motion would permanently override the live motion state
from camera_activity.motion. In useCameraActivity the detectingMotion
check (truthy string "OFF") took priority over camera_activity.motion,
so activeMotion was always false even with real motion present.

Now applyCameraActivity writes state.motion → {camera}/motion ("ON"/
"OFF"), keeping it in sync with the authoritative camera_activity data.

https://claude.ai/code/session_019B4dJXtcxvHn97ZaqHUB62
2026-03-18 05:21:29 +00:00
ibs0d
ea9d96d64c
Merge branch 'blakeblackshear:dev' into dev 2026-03-18 13:24:52 +11:00
Claude
621f484b92
fix: accept boolean | undefined for CameraMotionDot autoLive prop
autoLive ?? globalAutoLive can be undefined when useUserPersistence
hasn't hydrated yet. Change the prop type to optional boolean and
treat undefined as the default-true value (show dot unless explicitly
set to false via no-streaming mode).

https://claude.ai/code/session_019B4dJXtcxvHn97ZaqHUB62
2026-03-17 07:39:59 +00:00
Claude
f9885df0e4
fix: replace callback motion dot with direct WS subscription component
The previous approach (useEffect → onActiveMotionChange callback →
parent state update) was unreliable: the dot only appeared if motion
was active at the moment of initial mount but did not react to
subsequent WS motion events.

Root cause: the intermediate state chain breaks because React's
useEffect batching and component re-render timing can cause the
parent state to lag behind or miss updates when motion changes after
mount.

Fix: replace the mechanism entirely with a dedicated CameraMotionDot
component that calls useCameraActivity directly. Being a proper React
component it subscribes to the {camera}/motion WS topic via
useSyncExternalStore and re-renders immediately and reliably whenever
motion state changes — no intermediate callbacks or parent state needed.

- Remove onActiveMotionChange prop from LivePlayer; add showMotionDot
  boolean prop (default true) to suppress the internal dot in grid view
- Remove cameraMotionStates state and setCameraMotionStates from
  DraggableGridLayout
- Add CameraMotionDot component with direct useCameraActivity hook

https://claude.ai/code/session_019B4dJXtcxvHn97ZaqHUB62
2026-03-17 03:10:46 +00:00
ryzendigo
e80da2b297
fix: WebSocket connection leaked on WebRTC player cleanup (#22473)
The connect() function creates a WebSocket but never stores the
reference. The useEffect cleanup only closes the RTCPeerConnection
via pcRef, leaving the WebSocket open.

Each time the component re-renders with changed deps (camera switch,
playback toggle, microphone toggle), a new WebSocket is created
without closing the previous one. This leaks connections until the
browser garbage-collects them or the server times out.

Store the WebSocket in a ref and close it in the cleanup function.
2026-03-16 06:47:07 -06:00
ryzendigo
dfc6ff9202
fix: variable shadowing silently drops object label updates (#22472)
When an existing tracked object's label or stationary status changes
(e.g. sub_label assignment from face recognition), the update handler
declares a new const newObjects that shadows the outer let newObjects.

The label and stationary mutations apply to the inner copy, but
handleSetObjects on line 148 reads the outer variable which was never
mutated. The update is silently discarded.

Remove the inner declaration so mutations apply to the outer variable
that gets passed to handleSetObjects.
2026-03-16 06:43:25 -06:00
Claude
81f0164e9c
fix: show motion dot regardless of liveReady in grid callback path
The motionVisible condition gated the external dot (via onActiveMotionChange
callback) on liveReady, causing the dot to stay hidden for cameras in
continuous mode (showStillWithoutActivity=false) while the stream is loading
or reconnecting. Since the parent (DraggableGridLayout) renders the dot
outside the stream viewport, it should reflect actual motion state without
depending on stream load status.

Simplify the callback-path effect to use !!(autoLive && !offline &&
activeMotion) so the dot appears in the grid card whenever motion is active.
The full condition (including liveReady) is still used for the inline dot
rendered inside LivePlayer when no callback is provided.

https://claude.ai/code/session_019B4dJXtcxvHn97ZaqHUB62
2026-03-16 12:05:26 +00:00
Claude
3ba0f1e230
fix: enable stats collection when onStatsUpdate callback is provided
getStats was always passed showStats (false in grid view), so underlying
players never collected stats data. Now uses showStats || !!onStatsUpdate
so players collect stats whenever the external callback is present.

https://claude.ai/code/session_019B4dJXtcxvHn97ZaqHUB62
2026-03-16 10:14:41 +00:00
Claude
89864e364d
fix: prevent infinite render loop in zoom overlay callbacks
Use useRef to store onStatsUpdate/onLoadingChange/onActiveMotionChange
callbacks so useEffect deps don't include the callback references.
Inline arrow functions in .map() change identity every render, causing
the previous useEffect([stats, onCallback]) to re-fire on each parent
re-render, triggering another setState → re-render → infinite loop.

https://claude.ai/code/session_019B4dJXtcxvHn97ZaqHUB62
2026-03-16 09:54:16 +00:00
Claude
9307272007
fix: exclude stats, spinner and motion dot from camera zoom transform
Move PlayerStats, ActivityIndicator and motion dot rendering outside the
zoom transform div in DraggableGridLayout so they are not scaled when
the user zooms with Shift+Wheel.

- Add onStatsUpdate, onLoadingChange, onActiveMotionChange callback props
  to LivePlayer; when provided, suppress the internal overlay elements
  and bubble state up to the parent instead
- In DraggableGridLayout, maintain per-camera overlay states and render
  the three overlays as siblings to the zoom div (inside the clipping
  viewport) so they remain at natural size regardless of zoom level

https://claude.ai/code/session_019B4dJXtcxvHn97ZaqHUB62
2026-03-16 09:08:43 +00:00
Claude
8f4063d162
Remove rounded corners from CameraImage (visible during stream load)
https://claude.ai/code/session_01THf2SuS7hLt9NgstxvKdg8
2026-03-16 08:34:38 +00:00
Claude
16281f669a
Remove rounded corners from LivePlayer component
https://claude.ai/code/session_01THf2SuS7hLt9NgstxvKdg8
2026-03-16 08:18:40 +00:00
Claude
61d399db05
Remove rounded corners from grid camera cards
https://claude.ai/code/session_01THf2SuS7hLt9NgstxvKdg8
2026-03-16 08:05:51 +00:00
Claude
b22dc4c946
Fix unused marginValue variable after removing grid spacing
Removed marginValue state and useLayoutEffect that calculated font size,
and removed unused useLayoutEffect import.

https://claude.ai/code/session_01THf2SuS7hLt9NgstxvKdg8
2026-03-16 07:36:46 +00:00
Claude
b7b5b08d53
Remove grid spacing between cards and from edges
Set margin and containerPadding to [0,0] in ResponsiveGridLayout,
removed px-2/my-2/pb-8 from the wrapper div, and updated cellHeight
formula to not account for margins.

https://claude.ai/code/session_01THf2SuS7hLt9NgstxvKdg8
2026-03-16 07:15:11 +00:00
Claude
710dec00fe
fix: use callback ref to measure container width after skeleton unmounts
useLayoutEffect with [] deps only ran on the initial render when
gridContainerRef was null (grid div was hidden behind skeleton).
After skeleton disappeared the div mounted but useLayoutEffect never
re-ran, leaving containerWidth=0 and Responsive invisible (blank screen).

A callback ref fires every time the element mounts, so containerWidth
is always set immediately when the grid div first appears.
2026-03-16 04:12:48 +00:00
Josh Hawkins
310b5dfe05
UI tweaks and fixes (#22448)
* fix double scrollbar in debug replay

* always hide ffmpeg cpu warnings for replay cameras

* add slovenian

* fix motion previews on safari and ios

match the logic used in ScrubbablePreview for manually stepping currentTime at the correct rate

* prevent motion recalibration when opening motion tuner
2026-03-15 07:26:23 -05:00
Claude
067fdb50e1
fix: replace useResizeObserver with useLayoutEffect for reliable container width
useResizeObserver reads ref.current during render (before commit), so on
first render ref.current is null, no observation starts, and containerWidth
stays 0 if no subsequent re-render happens (e.g. page refresh with cached
SWR data). useLayoutEffect runs after refs are committed, so ref.current
is always the real DOM element.

This fixes both the right-column overflow (no window.innerWidth fallback
needed — width is always the actual container width) and the black screen
on refresh (containerWidth is reliable before the first paint).

https://claude.ai/code/session_01H1sqbcFmtwwsdNTJcJHJWd
2026-03-15 12:03:54 +00:00
Claude
5e40dbbcd2
fix: reliably init grid width on page refresh using useLayoutEffect
useResizeObserver reads ref.current at render time; on page refresh with
fast SWR cache, no re-render occurs after mount so ref.current remains null
in the effect, observation never starts, and containerWidth stays 0 forever.

Add a useLayoutEffect that measures offsetWidth synchronously before paint
as a seed value (effectiveWidth = containerWidth || initialWidth). Once
ResizeObserver fires normally, containerWidth takes over. The Responsive
grid is gated on effectiveWidth > 0 so it always renders correctly on both
first load and refresh.

https://claude.ai/code/session_01H1sqbcFmtwwsdNTJcJHJWd
2026-03-15 11:32:13 +00:00
Claude
d39590604f
fix: prevent grid right-edge overflow by gating Responsive on containerWidth
Gate <Responsive> rendering on containerWidth > 0 so it only mounts after
ResizeObserver has measured the container. Use availableWidth directly as
the width prop (no window.innerWidth fallback) since the component now only
renders when containerWidth is known. This prevents the grid from rendering
wider than its container (which caused the rightmost column to overflow the
right edge).

https://claude.ai/code/session_01H1sqbcFmtwwsdNTJcJHJWd
2026-03-15 11:03:26 +00:00
Claude
84f3b16461
Fix DraggableGridLayout initial height collapse due to nullish coalescing bug
availableWidth starts at 0 (not null/undefined) before ResizeObserver fires.
The ?? operator passes 0 through instead of falling back to window.innerWidth,
making cellHeight negative and causing react-grid-layout to render a ~10px
container. The overflow-x-hidden div then becomes an implicit scroll container,
producing the 'cards squeezed in a small rectangle' symptom.

Changing ?? to || makes 0 trigger the window.innerWidth fallback, giving a
reasonable initial rowHeight until the real container width is measured.

https://claude.ai/code/session_01H1sqbcFmtwwsdNTJcJHJWd
2026-03-15 10:15:52 +00:00
ibs0d
a0ca322129
revert Update RecordingView.tsx 2026-03-15 14:31:11 +11:00
ibs0d
86de033f74
Merge branch 'blakeblackshear:dev' into dev 2026-03-15 13:55:05 +11:00
ibs0d
0c78103e56
Revert "Claude/fix video stretch history ans qf" 2026-03-15 13:38:11 +11:00
ibs0d
14709511f0
Revert "Claude/fix video stretch history ans qf" 2026-03-15 13:37:31 +11:00
ibs0d
b6b3c970c2
Revert "Add config useSWR to DetectionReview component" 2026-03-15 13:36:49 +11:00
ibs0d
f35a257844
Update RecordingView.tsx 2026-03-14 16:34:48 +11:00
Claude
128f016b5a
Add config useSWR to DetectionReview component
config was referenced but not available in DetectionReview scope.

https://claude.ai/code/session_01EwdaKGsrRLZ74smmCQ1MgW
2026-03-14 04:08:12 +00:00