Commit Graph
28687 Commits
Author SHA1 Message Date
Isaac Connor a7a1244eae Merge pull request #4967 from IgorA100/patch-225403
Clear mseSourceBuffer only if the number of buffers is greater than zero. (MonitorStream.js)
2026-07-05 13:04:07 -04:00
Isaac ConnorandClaude Opus 4.8 326e1b0ab2 feat: synthesize in_frame from the captured image when not decoded
Capture paths that deliver a raw Image without an ffmpeg decode (e.g.
LocalCamera/V4L2) left packet->in_frame null even though the pixels were
already present, so anything expecting a decoded frame failed. In
particular YChannel analysis called get_y_image(), which needs
in_frame->data[0], and logged "Can't get y_image without frame".

At the end of Monitor::Decode(), when a packet has an image but no
in_frame, wrap the image's planes in an AVFrame via Image::PopulateFrame
(av_image_fill_arrays over a dont_free buffer ref: pointers, no copy).
Any format is populated; consumers that need a specific layout check for
themselves (get_y_image now reports RGB has no Y plane rather than "no
frame").

Done after PHASE 5 so the frame reflects the oriented/masked image and we
don't re-orient a shared Y plane, and after the codec phases so
transfer_hwframe is never called with the null codec context a
non-decoding camera has. videostore is unaffected: it prefers
packet->image for frame data and derives pts from packet->timestamp.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 12:59:18 -04:00
IgorA100 141023dd98 Clear mseSourceBuffer only if the number of buffers is greater than zero. (MonitorStream.js) 2026-07-05 17:41:45 +03:00
Isaac ConnorandClaude Opus 4.8 92ab811720 feat: publish analysis images through a shared-memory ring
Replace the single alarm_image slot with an analysis_image_buffer ring of
image_buffer_count Images living in the already-reserved alarm_images SHM
region. Successive WriteAlarmImage calls rotate through the ring and
publish last_analysis_index last (after the bytes and per-slot format),
so a reader sampling last_analysis_index always sees a fully written
slot. GetAlarmImage returns that slot, syncing its AVPixelFormat from the
per-slot analysis_image_pixelformats array.

SharedData gains last_analysis_index and analysis_image_count (plus 8
bytes of padding to keep the 16-byte-multiple layout), making it 888
bytes. The Perl (Memory.pm) and PHP (Monitor.php) SHM readers are updated
in lockstep, and a static_assert(sizeof(SharedData)==888) in zm_monitor.h
guards the layout against silent drift.

This lets multiple in-flight analysis/annotated frames be buffered and
streamed in sync rather than always overwriting one slot, and gives the
AI object-detection work a place to publish annotated frames.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 14:32:51 -04:00
Isaac ConnorandClaude Opus 4.8 e2ed9287d7 fix: correct PHP SHM SharedData/TriggerData field offsets
The C++ SharedData struct is naturally aligned, not packed, so the
compiler inserts a 4-byte pad before capture_fps (after state) and
another before the startup_time union (after audio_channels). Monitor.php
used the naive packed offsets, so every field from capture_fps onward
(capture_fps/analysis_fps, latitude/longitude, the time fields,
alarm_cause and all of TriggerData) was read from an address 4-8 bytes
too low, yielding garbage.

Correct the offsets to the real aligned layout (SharedData is 872 bytes,
TriggerData starts at 872), matching what ZoneMinder::Memory computes and
what the C++ writes. No struct change; this is a reader-side fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 14:30:23 -04:00
Isaac ConnorandClaude Opus 4.8 944f8f2ece feat: add icons and labels to AI options view buttons
Give the Add/Delete buttons on the AI Datasets, Models and Classes
option tabs the same treatment as the console: an add_circle icon on
the add button and a delete (trash) icon on the delete button, each
with a labelled text span.

Add the missing AddNewClass/AddNewDataset/AddNewModel language strings
so the add buttons read "Add New Class/Dataset/Model" instead of the
raw translation keys.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:24:50 -04:00
Isaac Connor e417e95008 Fix page reload when changing monitor type 2026-07-02 20:24:50 -04:00
Isaac ConnorandClaude Opus 4.8 787722d103 feat: add AI dataset/model/class management UI to Options
Add three Options tabs (AI Datasets, AI Models, AI Classes) with full
CRUD, backed by the AI_* tables:

- List views (_options_ai_{datasets,models,classes}.php), edit modals
  (ajax/modals/ai_{dataset,model,class}.php) and action handlers
  (actions/ai_{dataset,model,class}.php).
- options.js loads the modals over ajax, wires the Add/edit buttons and
  the AI Classes dataset filter.
- options.php dispatches the new tab includes; functions.php registers
  the three tabs in the Options sub-menu with readable labels (they are
  not Config categories, so they need explicit entries).
- actions/options.php routes object=ai_* deletes to the matching
  handler; saves post directly to view=ai_*.

All tabs and actions are gated on System permission.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:24:50 -04:00
Isaac ConnorandClaude Opus 4.8 d8d9452179 feat: add AI object-detection data model
Add the schema backing per-monitor object detection and the AI dataset/
model/class management UI:

- Monitors gains AnalysisImageOpacity and ObjectDetection,
  ObjectDetectionModel, ObjectDetectionObjectThreshold,
  ObjectDetectionNMSThreshold columns.
- New tables AI_Datasets, AI_Models, AI_Object_Classes,
  AI_Detection_Settings and AI_Detections.
- Seed the COCO 2017 dataset (80 classes) and default per-class
  detection settings via db/coco_dataset.sql.

Existing installs get zm_update-1.39.17.sql, which is idempotent and
adds the columns with their final VARCHAR(16) ObjectDetection shape
directly (no enum-churn intermediates). Fresh installs create the same
objects from zm_create.sql.in sourcing AI_Models.sql and
coco_dataset.sql. Both paths were verified to produce identical schema.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:24:50 -04:00
Isaac Connor f6f2e0fd7a Merge branch 'master' of github.com:ZoneMinder/zoneminder 2026-07-01 11:52:54 -04:00
Isaac Connor 71bfe50d47 Only hide event filters when not inline 2026-07-01 11:52:50 -04:00
Isaac Connor 346fa0e8a0 Improve error message when we fail to write capture jpg 2026-07-01 11:52:25 -04:00
Isaac ConnorandClaude Opus 4.8 66299ae7cf feat: make log Level and Component filters multi-select
Convert the Level and Component filter dropdowns on the system log view
into Chosen multi-selects so several values can be filtered at once. An
empty selection means "All" (shown via the placeholder), replacing the
former explicit All option.

- log.php: emit filterLevel[]/filterComponent[] with multiple + placeholder,
  read the remembered selection as an array (tolerating the legacy scalar),
  and drop the leftover ZM\Debug dumps of the component list
- js/log.js: send level/Component as arrays when non-empty; drop the
  redundant filterComponent change binding now that data-on-change wires it
- ajax/log.php: match with Level IN (...) / Component IN (...) using
  parameterized placeholders, and persist the selections as arrays in the
  session

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 11:50:32 -04:00
Isaac ConnorandClaude Opus 4.8 53afbafb84 feat: add realtime pacing option for ffmpeg file sources
Add a "realtime=1" (alias "re=1") ffmpeg input option, modeled on the
existing "loop" option and equivalent to ffmpeg's -re flag. When set on a
file source, FfmpegCamera throttles packet delivery to the rate implied by
the stream timestamps instead of reading the file as fast as possible.

The option is parsed out of the ffmpeg Options string in OpenFfmpeg and
consumed so it is not passed to the demuxer or decoder. Capture() anchors
wall-clock time to the first delivered packet and sleeps before each
subsequent packet so it is not delivered ahead of schedule. Pacing uses dts
(monotonic in read order) with a pts fallback, runs after the existing
drop/jump filters, and re-anchors on backward jumps or gaps beyond a 10s cap
to avoid stalling on a discontinuity. Works alongside loop, whose
offset-adjusted timestamps stay monotonic across restarts.

The pacing decision is factored into a pure ComputeRealtimePace() function
and unit-tested in tests/zm_ffmpeg_camera.cpp (8 cases covering full/partial
interval waits, behind-schedule, on-schedule, backward jump, over-cap, and
the cap boundary).

Ported from the ai_server branch onto master's OpenFfmpeg structure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 10:08:58 -04:00
Isaac ConnorandClaude Opus 4.8 c9be205846 feat: loop file-based ffmpeg input on EOF via loop=1 option
When an ffmpeg monitor reads a seekable file source (e.g. file:///x.mp4)
and reaches the end, it previously failed and reconnected. Add a loop
mode, enabled with loop=1 in the monitor Options, that seeks back to the
start and continues instead.

Packet pts/dts are shifted by a per-stream absolute offset recomputed on
each loop (last emitted dts + frame duration - stream start_time) so
timestamps stay monotonically increasing for analysis and recording. The
loop option is consumed before passing options to ffmpeg (both the demuxer
in OpenFfmpeg and the decoder), and non-seekable inputs fall through to the
normal reconnect path.

Ported from the ai_server branch onto master's OpenFfmpeg structure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 10:08:58 -04:00
Isaac Connor 40beef8e6c Merge pull request #4961 from IgorA100/patch-714156
Fix: Hide canvas when destroying (audioMotionAnalyzer.js)
2026-06-30 17:36:53 -04:00
IgorA100 fdbb0b8280 Fix: Hide canvas when destroying (audioMotionAnalyzer.js)
The issue arose after we started using our own canvas instead of the generated audioMotion (PR #4888)
2026-06-29 23:18:17 +03:00
Isaac Connor f47ccb2909 Merge pull request #4959 from IgorA100/patch-463759
More correct and simpler handling of hiding the darkening background for bootstrap modal windows (except the first one)
2026-06-28 00:08:58 -04:00
IgorA100 dfe85959ab Discontinue using the 'show.bs.modal' and 'hidden.bs.modal' events; now use only CSS. (skin.js) 2026-06-27 23:42:59 +03:00
IgorA100 235a78d7fb More correct and simpler handling of hiding the darkening background for bootstrap modal windows (except the first one) (skin.css) 2026-06-27 23:32:42 +03:00
Isaac Connor f04b3613ac Merge branch 'menu-customization' 2026-06-27 12:59:15 -04:00
Isaac Connor 356e3226cc Merge branch '4946-videostore-null-stream-guard' 2026-06-27 12:59:15 -04:00
Isaac Connor ad6afadbb6 Merge branch '4943-conf-line-continuation' 2026-06-27 12:59:15 -04:00
Isaac Connor ef054f10ec Merge branch '4944-monitor-insert-error-surface' 2026-06-27 12:59:15 -04:00
Isaac ConnorandClaude Opus 4.8 8b9546df33 fix: fall back from go2rtc player when no video is delivered
When a go2rtc player connects but the source video codec cannot be decoded by
the browser (e.g. an H.265 camera viewed in Chrome, which supports HEVC over
neither WebRTC nor MSE), go2rtc negotiates the video track as inactive and
sends only audio. The video element "plays" audio with no picture and stays at
0x0, so the normal 'error' handler never fires and the player hangs on
"Loading..." indefinitely.

Add a watchdog armed when a go2rtc stream starts: if no video frame is decoded
within NO_VIDEO_TIMEOUT, register a playback error and advance through the
existing player-priority fallback (eventually ZMS MJPEG), which can render the
stream server-side. The watchdog is cleared on stop()/restart() so it never
fires against a subsequently selected player.

Verified in-browser on a 3840x2160 H.265 monitor: go2rtc_webrtc -> go2rtc_mse
-> zms, ending on a visible MJPEG image instead of an endless "Loading...".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 12:41:52 -04:00
Isaac Connor 5fadb760e5 Merge branch 'master' of github.com:ZoneMinder/zoneminder 2026-06-27 12:41:37 -04:00
Isaac ConnorandClaude Opus 4.8 1123e9bfd6 fix: stop watch auto-scale shrinking video when status bar wraps
On the Watch page with Scale=Auto, the live status bar is constrained to the
scaled video width. For a wide/high-resolution camera shown via the go2rtc
player, the Auto fit shrank the video, which narrowed the status bar, which
wrapped its items onto more lines and grew taller. scaleToFit() counts that
height as overhead and shrank the video further on the next ResizeObserver
pass, so the video collapsed in visible steps (e.g. 249->181->29px, then
snapping back to full width via the negative-height fallback, and repeating).

Keep each status item on a single line and truncate with an ellipsis so the
status bar has a fixed height. This breaks the feedback loop; the Auto fit now
converges on the first pass.

Verified in-browser on a 3840x2160 go2rtc monitor: height stabilises instead
of oscillating.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 12:39:34 -04:00
Isaac ConnorandClaude Opus 4.8 3d5de347a4 fix: enforce authorization on API events/archive action
The REST API archive action toggled an event's Archived
(retention-protection) flag with no authorization beyond the
controller's coarse "Events permission is not None" gate. Any
authenticated read-only user, including one restricted to a subset of
monitors, could flip the retention state of any event by enumerating
event ids, and the action was reachable over GET (CSRF-able).

Gate the write by direction: archiving (protects from purge) requires
view access via Event::canView(); un-archiving (re-exposes to purge)
requires edit access via Event::canEdit(). Both enforce the per-monitor
object-level ACL. Restrict the action to POST/PUT to block CSRF.

Addresses GHSA-5v9h-ww7p-hxgv.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 12:32:51 -04:00
Isaac ConnorandClaude Opus 4.8 48fadaebbf feat: customizable menu entries with links, icons and delete on options menu tab
Port the menu customization work from the ai_server branch onto master:
- Add/edit/delete custom navbar/sidebar menu entries via Options > Menu
- Per-entry Link column (new Menu_Items.Link) with ?view= fallback derived
  the same way as built-in items; custom entries render via buildMenuItem
- Live icon preview (material/font awesome) with fixed-width preview cell
- Per-row delete icon; add/reset buttons with tooltips
- Surface DB errors when saving options config: capture the swallowed PDO
  error via dbLastError() and show it in the page error banner instead of
  redirecting past it

Menu_Items.Link is added to zm_create.sql.in and as an idempotent ALTER in
zm_update-1.39.16.sql.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 12:13:49 -04:00
Isaac ConnorandClaude Opus 4.8 ac5fe49d57 fix: guard against null video out stream in encode path fixes #4946
avformat_new_stream() can return NULL, and the encode/transcode branch
dereferenced it immediately via ->codecpar when calling
avcodec_parameters_from_context, segfaulting zmc on the Event recording
thread (SIGSEGV, fault address 0xc). The encoder opens fine; it is the
output stream allocation that comes back NULL.

Check the return value and fail open() gracefully, matching the guard
already present on the PASSTHROUGH path a few lines above.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 12:13:49 -04:00
Isaac ConnorandClaude Opus 4.7 e792020bb4 feat: support backslash line continuation in conf.d parsers fixes #4943
The C++ and PHP parsers for zm.conf and /etc/zm/conf.d/*.conf used fgets
with a 512-byte buffer, silently truncating any line longer than that.
There was also no way to split a value across multiple lines, so editors
hit the cap with no workaround.

Accept a trailing backslash (with optional whitespace before the newline)
as a line-continuation marker. Leading whitespace on continuation lines
is stripped so users can indent for readability without it leaking into
the value. Three parsers all read the same files and must agree:

- src/zm_config.cpp: switch to std::ifstream + std::getline so a single
  physical line is no longer capped at 512 bytes, then join continuation
  lines before running the existing pointer-based parser
- scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in: accumulate a logical
  line across trailing-backslash physical lines
- web/includes/config.php.in: drop the 512-byte fgets cap and accumulate
  in the same way

tests/zm_config.cpp covers three cases against the C++ parser: a
three-segment continuation joins to "firstsecondthird" with leading
whitespace stripped, a bare backslash inside a value is preserved
(C:\Users\zm), and a 1500-byte single line survives intact.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-27 12:13:49 -04:00
Isaac ConnorandClaude Opus 4.7 f26dee598e fix: surface DB error when new monitor insert fails fixes #4944
The insert branch only logged via ZM\Error and returned, leaving
$error_message empty. The dispatcher then re-rendered views/monitor.php
with no banner and the user saw an apparent success that silently
discarded their new monitor.

Append $monitor->get_last_error() to $error_message so the existing
<div id="error"> in getBodyTopHTML() shows the actual DB message, and
keep the early return so the user stays on the edit view with the form
values preserved by views/monitor.php's $_REQUEST['newMonitor'] handling.
Matches the pattern already used on the update path at line 273.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-27 12:07:22 -04:00
Isaac Connor de50700177 Merge pull request #4951 from IgorA100/patch-620074
For live playback (when hovering over thumbs) using RTSP2Web and Go2RTC, use the actual video aspect ratio, not the thumbs image.
2026-06-27 11:46:58 -04:00
Isaac Connor 72d3b6e6ea Merge pull request #4950 from IgorA100/patch-467557
Play the second channel using Go2RTC on the Console page if it is available
2026-06-27 11:45:52 -04:00
Isaac Connor 59e57ca9fd Merge pull request #4954 from IgorA100/patch-826761
Fix: When Go2RTC generates an error message (not a Go2RTC error itself!), also register the error using the streamErrorRegistration() method and restart the stream.  (video-stream.js)
2026-06-27 11:44:15 -04:00
Isaac Connor f8fa5147f1 Merge pull request #4955 from IgorA100/patch-622555
Fix: Display the log page refresh status as "stopped" only while processing selected lines, not when a new request occurs on Log page
2026-06-27 11:43:32 -04:00
Isaac Connor d98695d28d Merge pull request #4956 from IgorA100/patch-687880
Fix: Also display startRow and stopRow according to toLocaleString() on Log page
2026-06-27 11:42:49 -04:00
Isaac ConnorandClaude Opus 4.8 5e499347f6 fix: link stdc++fs into libzm for std::filesystem on GCC 8
zm_eventstream.cpp uses std::filesystem, which compiles into libzm.a.
On Rocky 8 (GCC 8) std::filesystem lives in a separate libstdc++fs that
must be linked explicitly. FILESYSTEM_LIBRARY was only attached to zma,
so zmc, zms, zmu, zmbenchmark and zm_rtsp_server failed to link with
undefined references to std::filesystem symbols.

Attach FILESYSTEM_LIBRARY to the zm library as PUBLIC so all consumers
inherit it transitively, and drop the redundant entry on zma.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:27:47 -04:00
Isaac ConnorandClaude Opus 4.8 cf92249820 fix: build zone alarm highlights in YUV420P to stop spurious Colourise warning
The "Target image is already colourised, colours: 1" warning fired from
Image::Colourise() during zone alarm overlay on YUV420P monitors, even with
ZM_COLOUR_JPEG_FILES off.

monitor->Colours() returns 1 for both GRAY8 and planar YUV420P (the GRAY8
alias), so zm_zone.cpp misclassified YUV420P monitors as grayscale and built
an RGB24 alarm highlight. Overlaying that RGB24 highlight onto the YUV420P
analysis_image hit the RGB-on-mono branch, which calls Colourise() - valid
only for GRAY8. Colourise() warned and bailed, after which the per-pixel loop
walked the 1.5x planar buffer as 3x packed RGB (out-of-bounds write).

Resolve the real capture format via zm_pixformat_from_colours() so true GRAY8
still upgrades to an RGB24 highlight while YUV420P keeps its format. Add a
YUV420P output branch to HighlightEdges (single alarm colour written to luma
and the shared chroma sample, transparent elsewhere) and a YUV420P-on-YUV420P
branch to Overlay (copies luma where non-zero, copies each chroma sample when
any covered luma pixel is marked). Colourise() is now only reached for GRAY8.

Add Catch2 coverage for the YUV420P Overlay masking and HighlightEdges output.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 16:10:07 -04:00
IgorA100 4040e61709 Also display startRow and stopRow according to toLocaleString() (log.js) 2026-06-26 19:19:40 +03:00
IgorA100 f5e171ee87 Fix: Display the log page refresh status as "stopped" only while processing selected lines, not when a new request occurs. (log.js)
First, this will speed up the display of the actual status.
Second, it will prevent incorrect status indication if rows were selected while executing the next AJAX request.

Also, update the status to "awaiting" only if the previous status was "stopped." This will ensure the status is displayed correctly on the first page refresh.
2026-06-26 17:30:29 +03:00
IgorA100 686ff39d44 When Go2RTC generates an error message (not a Go2RTC error itself!), also register the error using the streamErrorRegistration() method and restart the stream. (video-stream.js)
Fix: #4953
2026-06-26 15:29:29 +03:00
Isaac Connor d4020e6394 You can't just style li like that. No visible effect after removing it 2026-06-25 17:33:14 -04:00
Isaac Connor 356f22c9cc Warning=>Debug 2026-06-25 17:30:35 -04:00
IgorA100 d322dbabdf Use actual video aspect ratio for '#monitor-thumb-overlay' container (video-stream.js) 2026-06-25 21:07:25 +03:00
IgorA100 e8a2859dbf Perform a dimensions check before assigning to the container (skin.js) 2026-06-25 20:36:36 +03:00
IgorA100 5328ab7e3d For live playback (when hovering over thumbs) using RTSP2Web and Go2RTC, use the actual video aspect ratio, not the thumbs image. (skin.js) 2026-06-25 20:27:57 +03:00
IgorA100 50d4eaa1cc Specify the "data-stream-channel" attribute. (console.php) 2026-06-25 18:43:39 +03:00
IgorA100 dc1c4e231d Play the second channel using Go2RTC on the Console page if it is available (skin.js) 2026-06-25 18:40:59 +03:00
Isaac Connor 7cf71f82b2 Merge pull request #4928 from IgorA100/patch-51715
Many Log page improvements
2026-06-25 08:29:43 -04:00