MonitorStream.js initialized analyse_frames = true, causing zone
overlays to always appear on stream start regardless of view settings.
Change default to false so watch view starts clean. The zone editor
view already explicitly enables analysis after monitor creation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The lo_x/lo_y/hi_x/hi_y variables were unsigned int despite being assigned
from signed int32 Vector2 members and compared against signed int dimensions.
This caused scattered (int) and (unsigned int) casts throughout CheckAlarms,
std_alarmedpixels, and Setup. Switching to int eliminates the casts, fixes
the signed/unsigned comparison warnings, and makes the -1 sentinel check in
std_alarmedpixels explicit rather than relying on unsigned wraparound.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add config type entries for Hostname, DNS, NTP, NetworkInterfaces,
Capabilities, Scopes, Services, VideoSources, VideoSourceConfigurations,
AudioSources, AudioSourceConfigurations, AudioEncoderConfigurations,
StreamUri, SnapshotUri, ImagingOptions, PTZConfigurations, PTZNodes,
PTZPresets, and PTZStatus.
- Support __PROFILE_TOKEN__ substitution in request bodies
- Add writable flag to config types and enforce it in set_config
- Add NTP set handler for SetNTP ONVIF command
- Organize config_types by ONVIF service (device, media, imaging, PTZ)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a camera reconnects at a different resolution than the zone polygons
were configured for, the polygon bounding box can exceed the frame
dimensions. This caused a heap buffer overflow in the memset that zeros
the bbox rows of the diff image, confirmed by Valgrind (invalid write
at 0 bytes past a 921600-byte / 1280x720 block).
- Validate delta_image buffer and dimensions at entry
- Clamp hi_y/hi_x to image height/width before memset and pixel loops
- Guard filter, blob, and HighlightEdges loops against empty rows where
ranges[y].lo_x is -1 (would wrap to UINT_MAX as unsigned)
- Clamp extents in std_alarmedpixels independently for defense in depth
Warning() logs identify which zones need polygon reconfiguration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add --protocol, --address, and --device CLI options that bypass the
database and socket machinery to load a control module directly and
execute a single command. This enables standalone testing of control
modules (e.g. ONVIF.pm get_config) without a running ZoneMinder
instance. Also update POD with full documentation and examples.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
avformat_close_input() can block for 75-90s on TCP retransmit timeout
when an RTSP camera becomes unresponsive, and the connect() retry loop
also lacks heartbeat updates. This causes zmwatch to kill zmc with a
stale heartbeat even though the process is actively reconnecting.
Add SetHeartbeatTime() calls before/after Close() and in the connect()
retry loop so zmwatch knows zmc is still alive during reconnection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
App::uses('AppModel', 'CameraModel') tells CakePHP to look for AppModel
in a non-existent 'CameraModel' package. The correct second argument is
'Model', which points to app/Model/AppModel.php where the base class
actually lives.
This was likely a copy-paste error — every other model in the codebase
correctly uses App::uses('AppModel', 'Model'). The bug may go unnoticed
when another model loads AppModel first via CakePHP's autoloader, but
causes a fatal error if CameraModel is the first model resolved in a
request (e.g. hitting the camera models API endpoint directly).
Before limitPoints() silently clamps coordinates, check if any zone
point exceeds the monitor's image dimensions and show a warning icon
with tooltip in the zones table.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
AutoSelectFormat() opens the video device to enumerate formats, but
non-capture processes like zms (QUERY mode) should never need to access
the device. Guard the call with the existing capture flag so zms no
longer fails with "No such file or directory" errors when running under
Apache with systemd PrivateDevices=yes.
Also downgrade the fallback message from Error to Warning since YUYV
fallback is a recoverable condition, and add a hint about PrivateDevices
to the ENOENT error in AutoSelectFormat for cases where capture mode
legitimately can't open the device.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Debian Forky's stock apache2 systemd unit sets PrivateDevices=yes,
giving Apache its own /dev namespace without video devices. This
caused file_exists('/dev/video0') to return false even though the
device exists and www-data is in the video group.
Remove the file_exists() pre-check and just attempt v4l2-ctl directly.
When it fails, provide targeted diagnostics: missing v4l2-ctl, systemd
PrivateDevices detected, or generic permission error with the running
username.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The event thread was sleeping 33ms (ZM_SAMPLE_RATE) between checks for
packet decoded/analyzed status. Replace with a condition variable wait
on the packet itself, using a 2ms timeout as safety net for the race
between flag check and wait entry.
Add packet->notify_all() at every site where decoded or analyzed is set
to true, so the event thread wakes up near-instantly. Add wait_for()
to ZMPacketLock to support timed waits.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove per-packet ZMPacketLock trylock() from clearPackets() scan loops
and queuePacket() GOP deletion — the iterator check is sufficient since
threads only access packets through their own shared_ptr.
Add a monotonic uint64_t queue_index to ZMPacket, assigned on enqueue.
clearPackets() now finds the earliest iterator position with a single
min() over the 2-3 iterators, then uses one integer comparison per
scanned packet instead of walking all iterators per packet.
Defer packet destruction outside the mutex in both clearPackets() and
queuePacket() by collecting removed shared_ptrs into a local vector
and releasing the lock before they are destroyed.
Raise per-packet deletion Debug(1) to Debug(4) in both paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
If EventStartCommand/EventEndCommand contains a % character, use the
new token substitution (%EID%, %MID%, %EC%) with sh -c execution.
Otherwise, fall back to the original execlp() behavior that passes
event_id and monitor_id as $1 and $2, so existing installs are not
broken.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
cause can include trigger_data->trigger_cause (writable via zmtrigger
over the network) and zone labels (user-configured). Without escaping,
shell metacharacters in cause would be interpreted by sh -c.
Wraps cause in single quotes (with embedded single-quote escaping)
before substitution. %EID% and %MID% are safe as they are always
numeric from std::to_string.
Note on backward compatibility: the old execlp() passed event_id and
monitor_id as argv[1]/argv[2]. This PR intentionally does not preserve
that behavior — the old execlp() treated the entire command string as
an executable path, making it impossible to pass arguments, so any
working setup was already a simple path with no args. Users should
migrate to %EID%/%MID% tokens which are more explicit and flexible.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace execlp() with execl("/bin/sh") and substitute %EID%, %MID%,
and %EC% tokens before execution. This allows users to pass arguments
directly in the command string, e.g.:
/path/to/zm_detect.py -c /etc/zm/config.yml -e %EID% -m %MID% -r "%EC%" -n
Previously execlp() treated the entire command string as the executable
path, making it impossible to pass arguments without a wrapper script.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The post-save redirect included filter query params in the URL, which
caused the view to populate from request data instead of loading from
the database. Since the querystring omitted Name and UserId, both
fields appeared empty/wrong after reload. Remove the querystring from
the redirect so the view loads the saved filter from the database.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add new 'ONVIF' Controls entry with corrected brightness/contrast
ranges (0-100). Update legacy entries (ONVIF Camera, Netcat ONVIF,
Reolink RLC-423/411/420) to use Protocol 'ONVIF' instead of the
old per-vendor module names (onvif, Netcat, Reolink).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add configuration read/write support following the Uniview.pm pattern.
get_config() queries five ONVIF categories (DeviceInformation, DateTime,
ImagingSettings, VideoEncoderConfiguration, Profiles) via SOAP and returns
parsed XML as hashes. set_config() supports writing ImagingSettings
(Brightness/Contrast/Saturation/Sharpness/ColorSaturation) and DateTime,
treating other categories as read-only.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- onvif.pm: irisAbsClose checked lowercase 'brightness' so getCamParams
was never called on decrease, sending stale/zero values
- Reolink.pm: same brightness case bug, plus whiteAbsIn and whiteAbsOut
built the SOAP message but never called sendCmd — contrast changes
were silently discarded
- Netcat.pm: same brightness case bug, plus debug() call should be Debug()
- TapoC520WS_ONVIF.pm: same brightness case bug
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Single ONVIF SOAP/PTZ implementation that replaces onvif.pm, Reolink.pm,
Netcat.pm, and TapoC520WS_ONVIF.pm. All state is kept in instance variables
(no package globals), SSL verification falls back automatically, and
sendCmd routes to the correct ONVIF service endpoint per command type.
Bug fixes from the originals:
- Brightness decrease checked lowercase 'brightness' (never matched)
- whiteAbsIn/whiteAbsOut in Reolink/Netcat/TapoC520WS never called sendCmd
- Imaging commands sent to PTZ endpoint instead of /onvif/imaging
- Reboot sent to PTZ endpoint instead of /onvif/device_service
- Package globals leaked state between camera instances
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merge MAC vendor database from zm_utils into ZoneMinder. Adds entries for
Reolink, Dahua, Hanwha, Buffalo, Google, Sagemcom, and many additional
HikVision/Ubiquiti/Grandstream MACs. Sorted by vendor type for readability.
Removes duplicates from zm_utils source. Preserves ZM-only Uniview and
TP-Link entries. Keeps IC Realtime Dahua control mapping that zm_utils lost.
Updates Vivotek with ePTZ control and normalizes D-Link type name.
34 -> 73 entries.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix critical crash in open() where guess_credentials() boolean return was
treated as URI object. Rewrite open() to use base class get_realm() for
auth discovery, matching the Uniview/HikVision pattern.
Bug fixes:
- focusRelNear: remove double-$self in recursive call, use instance var
- focusRelFar: fix copy-paste error calling focusRelNear instead of self,
remove double-$self, use instance var
- focusAbs: fix .cgi.cgi typo in opticssetup URL
- Replace package-global $use_optics with $$self{use_optics} to prevent
state leaking between camera instances
- Remove unused our $uri, use ZoneMinder::Config, use URI imports
- Move use LWP::UserAgent to module-level imports
New features:
- get_config/set_config via Axis param.cgi API
- probe() for network camera discovery
- rtsp_url() returning standard Axis RTSP path
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace empty get_config/set_config stubs with working implementations
using Uniview's native LAPI JSON REST API. Modernize open() to use base
class guess_credentials() and get_realm() instead of duplicated auth
logic. Rename put() to PutCmd() to fix broken PTZ methods. Remove
package-level globals in favor of instance variables. Add lapi_get/
lapi_put helpers, probe via LAPI with ISAPI fallback, and rtsp_url().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add probeUniview() that queries the camera's LAPI for device model,
name, resolution, and codec. Uses RTSP main stream URL format
rtsp://ip:554/media/video1 and LAPI snapshot endpoint for thumbnails.
Includes OUI alias probeZhejiangUniviewTechnologiesCoLtd for IEEE
vendor name matching. Adds all 4 registered Uniview MAC OUI prefixes
(48ea63, 6cf17e, 88263f, c47905) to MacVendors.json.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Event::Run could block indefinitely in PacketQueue methods during normal
event closing (closeEvent from analysis thread), because their wait
predicates only check deleting/zm_terminate, not Event's terminate_ flag.
Three changes fix this:
- get_packet_no_wait: return immediately when iterator at end instead of
blocking on condition variable (makes it truly non-blocking)
- Event::Run: use increment_it(wait=false) since deletePacket can advance
the iterator to end() during AddPacket_ without the queue lock
- Event::Stop: call packetqueue->notify_all() to wake timed waits so
Run() checks terminate_ promptly
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The zone view enables analysis frames by default but the initial image
src and the rebuilt streaming URL were missing the analysis query param.
Add analysis=true to the getStreamHTML call in zone.php and preserve it
in MonitorStream.start() when switching from single to jpeg mode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cherry-picked src/ changes from edge branch commit 17450d122.
Adds ParsePercentagePolygon() to convert percentage-based zone coordinates
to pixel values using monitor dimensions. Zone::Load() now checks the Units
column to dispatch between legacy pixel parsing and percentage-based parsing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of copying the entire delta_image into the per-zone mask buffer
every frame (4MB memcpy for 4MP), allocate a persistent grayscale mask
and only zero the polygon bounding-box rows. alarmedpixels_row now
reads from delta_image (const, read-only) and writes threshold results
to the separate mask buffer.
Key changes:
- alarmedpixels_row takes separate pdelta (read) and pmask (write) pointers
- std_alarmedpixels accepts both delta_image (const) and mask_image
- CheckAlarms allocates mask with explicit linesize == width to match
filter/blob pointer arithmetic (avoids FFALIGN padding mismatch)
- Full buffer zero on allocation; bbox-only zero on reuse
- No functional change to filter, blob, or HighlightEdges stages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move the ISO 8601 time formatting function to zm_time.cpp/h so it is
reusable and not duplicated. Remove the local copies from
zm_monitor_onvif.cpp (was static) and tests/zm_onvif_renewal.cpp
(was a copy for testing). Both now use the shared declaration from
zm_time.h.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Restructure std_alarmedpixels inner loop so GCC/Clang can auto-vectorize
it at -O2/-O3. The compiler now emits 16-byte SIMD (SSE2/NEON) processing
16 pixels per iteration instead of 1.
Three changes enable this:
- Extract inner loop into static alarmedpixels_row() with __restrict__
on function parameters, giving the compiler a strong no-alias guarantee
- Use branchless bitwise AND instead of short-circuit && to avoid
branches that block vectorization
- Remove per-row Debug(7) call that clobbered memory from the compiler's
perspective, invalidating pointer analysis
The original hand-written SSE2 ASM (removed in 2011, commit 8e9ccfe1e)
had alignment restrictions and didn't use per-row polygon ranges. This
approach is portable, maintainable, and achieves equivalent throughput.
GCC confirms: "loop vectorized using 16 byte vectors"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace 13 global include_directories() calls with list(APPEND
ZM_INCLUDE_DIRS ...) in root CMakeLists.txt, then apply them scoped
to the zm static library target via target_include_directories() in
src/CMakeLists.txt.
Previously all library include paths (MySQL, OpenSSL, curl, zlib, JPEG,
pthread, PCRE, VLC, VNC, libunwind, GnuTLS, Mosquitto) leaked into
vendored deps (RtspServer, CxxUrl, bcrypt). Now only the zm target and
its dependents see them.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Provide default rtsp://<ip>/ monitor entry for cameras discovered
via ARP that lack a vendor-specific probe function, so they always
have a URL for the Add button
- Only render the Add button and populate ProbeResults when url is
non-empty, preventing the "No url in button" alert
- Fix curl_getinfo() called after curl_close() which broke HTTP
response body parsing in probe functions
- Add missing break in import switch case to prevent fall-through
to default warning
maybe fixes#4613
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace global add_definitions(-DWITH_GSOAP) and add_definitions(-DZM_STRIP_NEON=1)
with target_compile_definitions on the zm static library target. These defines are
only consumed by src/ files but were leaking into all vendored deps (RtspServer,
CxxUrl, bcrypt, etc).
WITH_GSOAP is PUBLIC on zm since the executables linking zm include headers
that check it. ZM_STRIP_NEON is PRIVATE since it's only used in zm_image.cpp.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>