* 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.
* remove unreachable FileNotFoundError catches around frame_manager.get
* clear the end time cache entry when a segment is moved
The successful move returned before the end_time_cache pop at the bottom of move_segment, so every kept segment left its entry, keyframes list included, in the cache for the life of the process. Removing the cache file through drop_segment clears the entry with it.
* drop the cache file when the recording destination already exists
When the destination mp4 already existed, move_segment returned without removing the cache file, so the next pass probed and resolved the same segment again, forever. The leftover file is now dropped along with its end time cache entry, the same as the exception path.
* pace restarts of exited record and audio ffmpeg processes
The watchdog loop runs every second, and an exited record or audio ffmpeg was restarted on every pass with no backoff. A camera that refuses connections, such as one rebooting or rejecting credentials, got a reconnect attempt and a dump of ffmpeg's logs every second until it came back. The restart is now gated on can_restart like the stale segment and detect paths.
* don't send a stray newline in the certsync tls probe
The probe piped echo into openssl s_client, which sent the newline to nginx as a request after the handshake, so nginx logged an empty "" 400 line every 60 seconds on any install with TLS enabled. Reading stdin from /dev/null closes the connection right after the handshake. The fingerprint and the failure logging are unchanged.
* require a record expire_interval of at least 1
An expire_interval of 0 or less made the cleanup loop iterate over an empty range, so the cleanup thread exited on startup and expired recordings, temp clips and previews were never removed.
* remove the transcription temp wav when whisper raises
The temp wav was only removed after a successful transcribe call, so every failed transcription left a file in the cache directory.
* don't kill the ipc responder when a described event is gone
handle_update_event_description used Event.get, so a GenAI description that finished after its event was deleted raised DoesNotExist on the inter-process REP thread. That thread died with a reply still owed, and every process waiting on the requestor hung until restart. A missing event is now skipped.
* size the birdseye frame from the snapped canvas shape
The birdseye writer sizes its shared memory with get_canvas_shape, which snaps an unsupported aspect ratio such as 1920x1200 to a supported one, but the tracked object processor read it with the raw config width and height. The sizes never matched, so get returned None on every call and /api/birdseye/latest.jpg returned a 500, which broke the Home Assistant birdseye entity's still image. The reader now uses the same canvas shape, and a missing frame during startup returns an error response instead of raising in cvtColor.
* remove dead disconnect timer from the mse player
The effect that scheduled a delayed disconnect when playback was disabled never ran its body. The connect effect's cleanup already calls onDisconnect, which clears wsRef before the later effect checks it.
* remove partial output when a recording move fails
A remux that failed partway, such as on a full disk, left a partial mp4 at the destination. The next pass saw the destination already existed and dropped the cache file instead of retrying, so the segment was never saved. The failed output is now removed so the next pass retries the move. Tests cover the success, existing destination and failed move paths of move_segment.
* keep frame shm open and skip overwritten frames
Every reader closed and reopened a frame's shared memory segment on each frame, so each process paid a page fault on every page of a fresh mapping (about 760 for a 1080p frame) per frame. Readers now keep segments mapped. Capture stamps each slot's frame time in a per-camera `{camera}_frame_times` segment, and `get()` returns None when the slot no longer holds the requested frame, reopening once by name so a mapping left over from a same-size camera recreate heals itself. Capture also reuses a slot when the frame queue is full, since no reader was told about it.
* verify the frame for embeddings object updates
Tracked object events only carried the object's own frame_time, which stays at its last detection when the object isn't detected in a frame, so embeddings couldn't check the slot it read and could crop face or plate data from a frame that had already been overwritten. Events now carry the time of the frame they name, and embeddings passes it to `get()`.
* log when a frame slot was overwritten before it was read
A lapped read and a missing segment ended up as the same debug line at every caller, so there was no way to tell a slow reader from a missing frame.
* bring the FrameManager interface in line with its implementation
The abstract get() still had a timeout_ms signature from an older implementation and frame_times() wasn't declared, though capture and detect are typed against the interface. get() also didn't say how long the array it returns stays valid.
* drop a camera's cached frame mappings when it is removed or recreated
A reader that kept its mappings open stayed on a removed camera's segments, which pinned their shared memory until restart, and after a same-size recreate the ledger reopen only refreshed the slot being read while the other cached slots still returned the old camera's pixels. Readers now drop every cached slot for a camera on its remove update, and get() does the same when a ledger mismatch turns out to be a replaced ledger rather than a lapped slot.
* match a camera's frame names exactly in close_camera
The prefix match on `{camera}_frame` also caught another camera whose name starts with it, so removing `cam` dropped the cached ring of a camera named `cam_frame`.
* Guard object processor queue handlers against unknown cameras
* Skip embeddings post processing for removed cameras
* End review segments for removed cameras
* Drop queued autotracker moves for removed cameras
* Release tracked event thumbnails when skipping a removed camera
* Add locked accessors for camera states
* Read camera states through the processor accessors
* Guard output and recording paths against cameras not yet known
* Resolve camera state once in ONVIF, notification, and transcription paths
* recreate review thumbnail directory before writing and log write failures
cleanup's remove_empty_directories() can rmdir an empty clips/review, after which thumbnail writes silently fail. Ensure the directory exists before both cv2.imwrite calls and check their return value
* add docs for add camera wizard
* Handle indefinite events when a segment needs to forcibly be ended for a ceamera
* update keyframe interval article link
---------
Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
* Pin ruff
* Add python upgrade fixes
This enables python upgrade checks in ruff to look for deprecated types and patterns. This namely fixes:
- usage of deprecated `Typing` which is now built in
- some specific exceptions which are caught and have new aliases
Some specific UP checks were also ignored as they are stylistic / unimportant and likely to cause bugs
* Remove async blocking calls
Use asyncio.to_thread on two remaining blocking calls to fix hanging event thread loop. Enable this specific rule to block it in the future.
* Use proper logging mechanism
* Correctly format logs
* Raise with context
When raising an exception include the from context to improve debugging
* Cleanup
* fix: operator precedence bug in detection type check
The condition:
topic == DetectionTypeEnum.api.value or DetectionTypeEnum.lpr.value
evaluates as:
(topic == DetectionTypeEnum.api.value) or (DetectionTypeEnum.lpr.value)
Since DetectionTypeEnum.lpr.value is a non-empty string (truthy), the
second operand is always True regardless of topic. The intended check
is whether topic matches either enum value:
topic == DetectionTypeEnum.api.value or topic == DetectionTypeEnum.lpr.value
* fix: apply same or operator fix to review/maintainer.py
Same issue as record/maintainer.py — the condition was always true
because the bare enum value is truthy.
* style: ruff format record/maintainer.py
* debug replay implementation
* fix masks after dev rebase
* fix squash merge issues
* fix
* fix
* fix
* no need to write debug replay camera to config
* camera and filter button and dropdown
* add filters
* add ability to edit motion and object config for debug replay
* add debug draw overlay to debug replay
* add guard to prevent crash when camera is no longer in camera_states
* fix overflow due to radix absolutely positioned elements
* increase number of messages
* ensure deep_merge replaces existing list values when override is true
* add back button
* add debug replay to explore and review menus
* clean up
* clean up
* update instructions to prevent exposing exception info
* fix typing
* refactor output logic
* refactor with helper function
* move init to function for consistency
* - API created events will be alerts OR detections, depending on the event label, defaulting to alerts
- Indefinite API events will extend the recording segment until those events are ended
- API event start time is the actual start time, instead of having a pre-buffer of record.event_pre_capture
* Instead of checking for indefinite events on a camera before deciding if we should end the segment, only update last_detection_time and last_alert_time if frame_time is greater, which should have the same effect
* Add the ability to set a pre_capture number of seconds when creating a manual event via the API. Default behavior unchanged
* Remove unnecessary _publish_segment_start() call
* Formatting
* handle last_alert_time or last_detection_time being None when checking them against the frame_time
* comment manual_info["label"].split(": ")[0] for clarity
* Update version
* Create scaffolding for case management (#21293)
* implement case management for export apis (#21295)
* refactor vainfo to search for first GPU (#21296)
use existing LibvaGpuSelector to pick appropritate libva device
* Case management UI (#21299)
* Refactor export cards to match existing cards in other UI pages
* Show cases separately from exports
* Add proper filtering and display of cases
* Add ability to edit and select cases for exports
* Cleanup typing
* Hide if no unassigned
* Cleanup hiding logic
* fix scrolling
* Improve layout
* Camera connection quality indicator (#21297)
* add camera connection quality metrics and indicator
* formatting
* move stall calcs to watchdog
* clean up
* change watchdog to 1s and separately track time for ffmpeg retry_interval
* implement status caching to reduce message volume
* Export filter UI (#21322)
* Get started on export filters
* implement basic filter
* Implement filtering and adjust api
* Improve filter handling
* Improve navigation
* Cleanup
* handle scrolling
* Refactor temperature reporting for detectors and implement Hailo temp reading (#21395)
* Add Hailo temperature retrieval
* Refactor `get_hailo_temps()` to use ctxmanager
* Show Hailo temps in system UI
* Move hailo_platform import to get_hailo_temps
* Refactor temperatures calculations to use within detector block
* Adjust webUI to handle new location
---------
Co-authored-by: tigattack <10629864+tigattack@users.noreply.github.com>
* Camera-specific hwaccel settings for timelapse exports (correct base) (#21386)
* added hwaccel_args to camera.record.export config struct
* populate camera.record.export.hwaccel_args with a cascade up to camera then global if 'auto'
* use new hwaccel args in export
* added documentation for camera-specific hwaccel export
* fix c/p error
* missed an import
* fleshed out the docs and comments a bit
* ruff lint
* separated out the tips in the doc
* fix documentation
* fix and simplify reference config doc
* Add support for GPU and NPU temperatures (#21495)
* Add rockchip temps
* Add support for GPU and NPU temperatures in the frontend
* Add support for Nvidia temperature
* Improve separation
* Adjust graph scaling
* Exports Improvements (#21521)
* Add images to case folder view
* Add ability to select case in export dialog
* Add to mobile review too
* Add API to handle deleting recordings (#21520)
* Add recording delete API
* Re-organize recordings apis
* Fix import
* Consolidate query types
* Add media sync API endpoint (#21526)
* add media cleanup functions
* add endpoint
* remove scheduled sync recordings from cleanup
* move to utils dir
* tweak import
* remove sync_recordings and add config migrator
* remove sync_recordings
* docs
* remove key
* clean up docs
* docs fix
* docs tweak
* Media sync API refactor and UI (#21542)
* generic job infrastructure
* types and dispatcher changes for jobs
* save data in memory only for completed jobs
* implement media sync job and endpoints
* change logs to debug
* websocket hook and types
* frontend
* i18n
* docs tweaks
* endpoint descriptions
* tweak docs
* use same logging pattern in sync_recordings as the other sync functions (#21625)
* Fix incorrect counting in sync_recordings (#21626)
* Update go2rtc to v1.9.13 (#21648)
Co-authored-by: Eugeny Tulupov <eugeny.tulupov@spirent.com>
* Refactor Time-Lapse Export (#21668)
* refactor time lapse creation to be a separate API call with ability to pass arbitrary ffmpeg args
* Add CPU fallback
* Optimize empty directory cleanup for recordings (#21695)
The previous empty directory cleanup did a full recursive directory
walk, which can be extremely slow. This new implementation only removes
directories which have a chance of being empty due to a recent file
deletion.
* Implement llama.cpp GenAI Provider (#21690)
* Implement llama.cpp GenAI Provider
* Add docs
* Update links
* Fix broken mqtt links
* Fix more broken anchors
* Remove parents in remove_empty_directories (#21726)
The original implementation did a full directory tree walk to find and remove
empty directories, so this implementation should remove the parents as well,
like the original did.
* Implement LLM Chat API with tool calling support (#21731)
* Implement initial tools definiton APIs
* Add initial chat completion API with tool support
* Implement other providers
* Cleanup
* Offline preview image (#21752)
* use latest preview frame for latest image when camera is offline
* remove frame extraction logic
* tests
* frontend
* add description to api endpoint
* Update to ROCm 7.2.0 (#21753)
* Update to ROCm 7.2.0
* ROCm now works properly with JinaV1
* Arcface has compilation error
* Add live context tool to LLM (#21754)
* Add live context tool
* Improve handling of images in request
* Improve prompt caching
* Add networking options for configuring listening ports (#21779)
* feat: add X-Frame-Time when returning snapshot (#21932)
Co-authored-by: Florent MORICONI <170678386+fmcloudconsulting@users.noreply.github.com>
* Improve jsmpeg player websocket handling (#21943)
* improve jsmpeg player websocket handling
prevent websocket console messages from appearing when player is destroyed
* reformat files after ruff upgrade
* Allow API Events to be Detections or Alerts, depending on the Event Label (#21923)
* - API created events will be alerts OR detections, depending on the event label, defaulting to alerts
- Indefinite API events will extend the recording segment until those events are ended
- API event start time is the actual start time, instead of having a pre-buffer of record.event_pre_capture
* Instead of checking for indefinite events on a camera before deciding if we should end the segment, only update last_detection_time and last_alert_time if frame_time is greater, which should have the same effect
* Add the ability to set a pre_capture number of seconds when creating a manual event via the API. Default behavior unchanged
* Remove unnecessary _publish_segment_start() call
* Formatting
* handle last_alert_time or last_detection_time being None when checking them against the frame_time
* comment manual_info["label"].split(": ")[0] for clarity
* ffmpeg Preview Segment Optimization for "high" and "very_high" (#21996)
* Introduce qmax parameter for ffmpeg preview encoding
Added PREVIEW_QMAX_PARAM to control ffmpeg encoding quality.
* formatting
* Fix spacing in qmax parameters for preview quality
* Adapt to new Gemini format
* Fix frame time access
* Remove exceptions
* Cleanup
---------
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
Co-authored-by: tigattack <10629864+tigattack@users.noreply.github.com>
Co-authored-by: Andrew Roberts <adroberts@gmail.com>
Co-authored-by: Eugeny Tulupov <zhekka3@gmail.com>
Co-authored-by: Eugeny Tulupov <eugeny.tulupov@spirent.com>
Co-authored-by: John Shaw <1753078+johnshaw@users.noreply.github.com>
Co-authored-by: Eric Work <work.eric@gmail.com>
Co-authored-by: FL42 <46161216+fl42@users.noreply.github.com>
Co-authored-by: Florent MORICONI <170678386+fmcloudconsulting@users.noreply.github.com>
Co-authored-by: nulledy <254504350+nulledy@users.noreply.github.com>
* Map verified objects to their sub label directly
* Simplify access
* Cleanup
* Add protection for mismatched object and index
* Keep track of verified objects separately
* Refactor active objects to class
* Keep segment going when detection is newer than end of alert
* Cleanup logic
* Fix
* Cleanup ending
* Adjust timing
* Improve detection saving
* Don't have padding at end for in progress reviews
* Add review config for cutoff times
* Move log level initialization to log
* Use logger config
* Formatting
* Fix config order
* Set process names
---------
Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
* Set runtime
* Use count correctly
* Don't assume camera sizes
* Use separate zmq proxy for object detection
* Correct order
* Use forkserver
* Only store PID instead of entire process reference
* Cleanup
* Catch correct errors
* Fix typing
* Remove before_run from process util
The before_run never actually ran because:
You're right to suspect an issue with before_run not being called and a potential deadlock. The way you've implemented the run_wrapper using __getattribute__ for the run method of BaseProcess is a common pitfall in Python's multiprocessing, especially when combined with how multiprocessing.Process works internally.
Here's a breakdown of why before_run isn't being called and why you might be experiencing a deadlock:
The Problem: __getattribute__ and Process Serialization
When you create a multiprocessing.Process object and call start(), the multiprocessing module needs to serialize the process object (or at least enough of it to re-create the process in the new interpreter). It then pickles this serialized object and sends it to the newly spawned process.
The issue with your __getattribute__ implementation for run is that:
run is retrieved during serialization: When multiprocessing tries to pickle your Process object to send to the new process, it will likely access the run attribute. This triggers your __getattribute__ wrapper, which then tries to bind run_wrapper to self.
run_wrapper is bound to the parent process's self: The run_wrapper closure, when created in the parent process, captures the self (the Process instance) from the parent's memory space.
Deserialization creates a new object: In the child process, a new Process object is created by deserializing the pickled data. However, the run_wrapper method that was pickled still holds a reference to the self from the parent process. This is a subtle but critical distinction.
The child's run is not your wrapped run: When the child process starts, it internally calls its own run method. Because of the serialization and deserialization process, the run method that's ultimately executed in the child process is the original multiprocessing.Process.run or the Process.run if you had directly overridden it. Your __getattribute__ magic, which wraps run, isn't correctly applied to the Process object within the child's context.
* Cleanup
* Logging bugfix (#18465)
* use mp Manager to handle logging queues
A Python bug (https://github.com/python/cpython/issues/91555) was preventing logs from the embeddings maintainer process from printing. The bug is fixed in Python 3.14, but a viable workaround is to use the multiprocessing Manager, which better manages mp queues and causes the logging to work correctly.
* consolidate
* fix typing
* Fix typing
* Use global log queue
* Move to using process for logging
* Convert camera tracking to process
* Add more processes
* Finalize process
* Cleanup
* Cleanup typing
* Formatting
* Remove daemon
---------
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
* Add base class for global config updates
* Add or remove camera states
* Move camera process management to separate thread
* Move camera management fully to separate class
* Cleanup
* Stop camera processes when stop command is sent
* Start processes dynamically when needed
* Adjust
* Leave extra room in tracked object queue for two cameras
* Dynamically set extra config pieces
* Add some TODOs
* Fix type check
* Simplify config updates
* Improve typing
* Correctly handle indexed entries
* Cleanup
* Create out SHM
* Use ZMQ for signaling object detectoin is completed
* Get camera correctly created
* Cleanup for updating the cameras config
* Cleanup
* Don't enable audio if no cameras have audio transcription
* Use exact string so similar camera names don't interfere
* Add ability to update config via json body to config/set endpoint
Additionally, update the config in a single rather than multiple calls for each updated key
* fix autotracking calibration to support new config updater function
---------
Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
* Fix the `Any` typing hint treewide
There has been confusion between the Any type[1] and the any function[2]
in typing hints.
[1] https://docs.python.org/3/library/typing.html#typing.Any
[2] https://docs.python.org/3/library/functions.html#any
* Fix typing for various frame_shape members
Frame shapes are most likely defined by height and width, so a single int
cannot express that.
* Wrap gpu stats functions in Optional[]
These can return `None`, so they need to be `Type | None`, which is what
`Optional` expresses very nicely.
* Fix return type in get_latest_segment_datetime
Returns a datetime object, not an integer.
* Make the return type of FrameManager.write optional
This is necessary since the SharedMemoryFrameManager.write function can
return None.
* Fix total_seconds() return type in get_tz_modifiers
The function returns a float, not an int.
https://docs.python.org/3/library/datetime.html#datetime.timedelta.total_seconds
* Account for floating point results in to_relative_box
Because the function uses division the return types may either be int or
float.
* Resolve ruff deprecation warning
The config has been split into formatter and linter, and the global
options are deprecated.
* face library i18n fixes
* face library i18n fixes
* add ability to use ctrl/cmd S to save in the config editor
* Use datetime as ID
* Update metrics inference speed to start with 0 ms
* fix android formatted thumbnail
* ensure role is comma separated and stripped correctly
* improve face library deletion
- add a confirmation dialog
- add ability to select all / delete faces in collections
* Implement lazy loading for video previews
* Force GPU for large embedding model
* GPU is required
* settings i18n fixes
* Don't delete train tab
* webpush debugging logs
* Fix incorrectly copying zones
* copy path data
* Ensure that cache dir exists for Frigate+
* face docs update
* Add description to upload image step to clarify the image
* Clean up
---------
Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
* config options
* processing in maintainer
* detect and process dedicated lpr plates
* create camera type, add manual event and save snapshot
* use const
* ensure lpr events are always detections, typing fixes
* docs
* docs tweaks
* add preprocessing and penalization for low confidence chars
* Monitor if camera is disabled for review items
* Simplify multi camera disabled check
* Cleanup birdseye config handling
* Cleanup
* Remove old listeners