pyzm package

ZoneMinder Client

The typed client for the ZoneMinder REST API. Returns dataclass models (Monitor, Event, Zone) instead of raw dicts.

Top-level ZoneMinder client – the one-stop public API.

Usage:

from pyzm import ZMClient

zm = ZMClient(api_url="https://zm.example.com/zm/api", user="admin", password="secret")

for m in zm.monitors():
    print(m.name, m.function)
    for z in m.get_zones():
        print(f"  zone: {z.name}")

for ev in zm.events(monitor_id=1, since="1 hour ago"):
    print(ev.id, ev.cause)
    ev.update_notes("detected")
class pyzm.client.ZMClient(api_url: str | None = None, user: str | None = None, password: str | None = None, *, portal_url: str | None = None, verify_ssl: bool = True, timeout: int = 30, db_user: str | None = None, db_password: str | None = None, db_host: str | None = None, db_name: str | None = None, conf_path: str | None = None, config: ZMClientConfig | None = None)

High-level ZoneMinder client.

Parameters:
  • api_url – Full ZM API URL (e.g. https://server/zm/api).

  • user – ZM username. None when auth is disabled.

  • password – ZM password.

  • portal_url – Full portal URL (e.g. https://server/zm). Auto-derived from api_url when not provided.

  • verify_ssl – Whether to verify SSL certificates. Set to False for self-signed certs.

  • config – A pre-built ZMClientConfig. When provided, all other keyword args are ignored.

__init__(api_url: str | None = None, user: str | None = None, password: str | None = None, *, portal_url: str | None = None, verify_ssl: bool = True, timeout: int = 30, db_user: str | None = None, db_password: str | None = None, db_host: str | None = None, db_name: str | None = None, conf_path: str | None = None, config: ZMClientConfig | None = None) None
property api: ZMAPI

The underlying low-level API (for advanced use).

property api_version: str | None
config(name: str) dict

Get a single config parameter by name.

configs() list[dict]

List all ZM configuration parameters.

delete_events(*, monitor_id: int | None = None, before: str | None = None, min_alarm_frames: int | None = None, limit: int = 100) int

Query events matching filters and delete each one. Returns count deleted.

disk_usage() dict

Get disk usage info.

event(event_id: int) Event

Fetch a single event by ID.

events(*, event_id: int | None = None, monitor_id: int | None = None, since: str | None = None, until: str | None = None, min_alarm_frames: int | None = None, object_only: bool = False, limit: int = 100) list[pyzm.models.zm.Event]

Query events with optional filters.

is_running() bool

Check if the ZM daemon is running.

monitor(monitor_id: int | str) Monitor

Return a single monitor by ID (int) or name (str, case-insensitive).

monitors(*, force_reload: bool = False) list[pyzm.models.zm.Monitor]

Return all monitors. Cached after first call.

notification(notification_id: int) Notification

Fetch a single notification registration by ID.

notifications() list[pyzm.models.zm.Notification]

Fetch all push notification token registrations.

restart() Any
servers() list[dict]

List all ZM servers (multi-server setups).

set_config(name: str, value: str) None

Set a config parameter value.

set_state(state: str) Any

Set ZM to a named state (e.g. ‘start’, ‘stop’, ‘restart’).

start() Any
states() list[dict]

List all available ZM states.

stop() Any
storage() list[dict]

List all storage areas with disk usage.

system_load() dict[str, float]

Get system load averages.

timezone() str

Get server timezone.

property zm_version: str | None

Logging

Logging utilities for pyzm. setup_zm_logging() provides ZM-native logging (file, database, syslog) matching Perl’s Logger.pm format.

pyzm.log – ZoneMinder-native logging via stdlib logging.

from pyzm.log import setup_zm_logging adapter = setup_zm_logging(name=”zmesdetect_m1”) adapter.Debug(1, “Hello from pyzm”) adapter.Info(“Something happened”)

class pyzm.log.ZMLogAdapter(logger: Logger, config: dict[str, Any], process_name: str)

Drop-in replacement for the pyzm.ZMLog module-level API.

Provides Debug, Info, Warning, Error, Fatal, close, and get_config methods that match the legacy interface.

Debug(level: int = 1, msg: str | None = None, caller: Any = None) None
Error(msg: str | None = None, caller: Any = None) None
Fatal(msg: str | None = None, caller: Any = None) None
Info(msg: str | None = None, caller: Any = None) None
Warning(msg: str | None = None, caller: Any = None) None
close() None
get_config() dict[str, Any]
pyzm.log.setup_zm_logging(name: str | None = None, override: dict[str, Any] | None = None) ZMLogAdapter

Initialize ZM-native logging and return a ZMLogAdapter.

This is the v2 replacement for pyzm.ZMLog.init(). It reads ZM config files, the DB Config table, and environment variables, then attaches appropriate handlers to a stdlib logger.

Parameters:
  • name – Log component name (e.g. "zmesdetect_m1").

  • override – Dict of config keys that override all other sources.

Machine Learning

The ML detection pipeline. Detector is the main entry point – it manages backends, model sequencing, and result filtering.

Top-level Detector – public API for pyzm v2 ML detection.

Usage:

from pyzm import Detector

# Auto-discover all models in the default path
det = Detector()

# Pick specific models by name (resolved from base_path)
det = Detector(models=["yolo11s", "yolo26s"])

# Custom model directory
det = Detector(models=["yolo11s"], base_path="/my/models")

# Detect
result = det.detect("/path/to/image.jpg")
class pyzm.ml.detector.Detector(config: DetectorConfig | None = None, models: list[str | pyzm.models.config.ModelConfig] | None = None, base_path: str | Path = '/var/lib/zmeventnotification/models', processor: str | Processor = Processor.CPU, *, gateway: str | None = None, gateway_mode: str = 'url', gateway_timeout: int = 60, gateway_username: str | None = None, gateway_password: str | None = None)

Top-level detection API.

Parameters:
  • config – A fully specified DetectorConfig. Takes precedence over models and base_path.

  • models – A convenience shorthand – a list of model name strings (e.g. ["yolo11s", "yolo26s"]) or ModelConfig objects. String names are resolved against base_path to find weight, config, and label files automatically.

  • base_path – Directory containing model subdirectories. Defaults to /var/lib/zmeventnotification/models. When models is None and config is None, all models in this directory are auto-discovered. When models contains name strings, they are resolved against this path.

  • processor – Hardware target for auto-discovered/resolved models. Accepts "cpu", "gpu", "tpu" or a Processor enum. Ignored when config is provided or when models contains ModelConfig objects (which carry their own processor).

  • gateway – URL of a remote pyzm.serve server (e.g. http://gpu:5000). When set, detect() sends images to the remote server instead of running inference locally.

  • gateway_mode"url" (default) sends frame URLs so the server fetches images directly from ZoneMinder. "image" sends JPEG-encoded frames instead. Only applies to detect_event(); single-image detect() calls always use image mode.

  • gateway_timeout – HTTP timeout in seconds for remote detection requests.

  • gateway_username – Username for remote server authentication (optional).

  • gateway_password – Password for remote server authentication (optional).

__init__(config: DetectorConfig | None = None, models: list[str | pyzm.models.config.ModelConfig] | None = None, base_path: str | Path = '/var/lib/zmeventnotification/models', processor: str | Processor = Processor.CPU, *, gateway: str | None = None, gateway_mode: str = 'url', gateway_timeout: int = 60, gateway_username: str | None = None, gateway_password: str | None = None) None
detect(input: str | np.ndarray | list[tuple[int | str, np.ndarray]], zones: list['Zone'] | None = None) DetectionResult

Run detection on one or more images.

Parameters:
  • input

    • str: path to an image file.

    • np.ndarray: a single BGR image array.

    • list[tuple[frame_id, np.ndarray]]: multiple frames. The best frame is chosen by frame_strategy.

  • zones – Optional detection zone polygons.

Return type:

DetectionResult

detect_audio(audio_path: str, event_week: int = -1, lat: float = -1.0, lon: float = -1.0) DetectionResult

Run audio detection on a standalone audio file.

This is a convenience method for running audio backends (e.g. BirdNET) on an audio file without a ZoneMinder event. Any format that ffmpeg can read (WAV, MP3, MP4, etc.) is supported.

Parameters:
  • audio_path – Path to an audio file.

  • event_week – ISO week number (1–48) for seasonal filtering. -1 disables.

  • lat – Latitude/longitude for location-based species filtering. -1 disables.

  • lon – Latitude/longitude for location-based species filtering. -1 disables.

Return type:

DetectionResult

detect_event(zm_client: ZMClient, event_id: int, zones: list['Zone'] | None = None, stream_config: StreamConfig | None = None) DetectionResult

Extract frames from a ZM event and run detection.

Parameters:
  • zm_client – A pyzm.client.ZMClient.

  • event_id – ZoneMinder event ID.

  • zones – Optional detection zones.

  • stream_config – Controls frame extraction (which frames, resize, etc.).

Return type:

DetectionResult

classmethod from_config(path: str) Detector

Load a Detector from a YAML configuration file.

The YAML is expected to have a top-level structure that can be parsed into a DetectorConfig.

classmethod from_dict(ml_options: dict) Detector

Build a Detector from an ml_sequence dict.

This delegates to DetectorConfig.from_dict() so existing YAML configurations work directly. If ml_options["general"] contains ml_gateway, the detector is created in remote mode.

ModelPipeline orchestrates the model sequence for a single frame.

It groups models by type (object, face, alpr), runs them in sequence with the configured match strategy and fallback, applies pre_existing_labels checks, and runs zone/size/pattern filters.

class pyzm.ml.pipeline.ModelPipeline(detector_config: DetectorConfig)

Runs the full detection pipeline for a single image frame.

The pipeline:

  1. Groups enabled models by ModelType (object, face, alpr).

  2. For each type (in config order), runs the model variants in sequence applying the configured MatchStrategy.

  3. Applies pre_existing_labels gates between types.

  4. Runs zone, size, and pattern filters on the combined results.

__init__(detector_config: DetectorConfig) None
load() None

Pre-load all enabled backends.

prepare() None

Create backend objects without loading weights (lazy mode).

Each backend will load its own weights on first detect() call thanks to the if self._model is None: self.load() guard in every backend’s detect() method.

run(image: np.ndarray, zones: list['Zone'] | None = None, original_shape: tuple[int, int] | None = None) DetectionResult

Run the full pipeline on image and return a DetectionResult.

Parameters:
  • image – BGR numpy array (OpenCV format).

  • zones – Optional list of Zone objects. If None, a full-image zone is used.

  • original_shape(height, width) of the image before resizing. When the image was resized for detection, zone polygons (which are in original coordinates) need to be rescaled to match. None means no rescaling is needed.

set_audio_context(audio_path: str | None, event_week: int = -1, monitor_lat: float = -1.0, monitor_lon: float = -1.0) None

Set audio file context for BirdNET / audio backends.

Pure functions for filtering detections.

Every function takes and returns Detection / BBox objects from pyzm.models.detection. Heavy dependencies (Shapely, pickle) are imported at function level so they remain optional.

pyzm.ml.filters.filter_by_pattern(detections: list[pyzm.models.detection.Detection], pattern: str) list[pyzm.models.detection.Detection]

Keep only detections whose label matches pattern (regex).

pyzm.ml.filters.filter_by_size(detections: list[pyzm.models.detection.Detection], max_size: str | None, image_shape: tuple[int, int]) list[pyzm.models.detection.Detection]

Filter detections whose area exceeds max_size.

max_size may be "50%" (of image area) or "300px" (absolute pixel area). If max_size is None or empty, all detections pass.

pyzm.ml.filters.filter_by_zone(detections: list[pyzm.models.detection.Detection], zones: list[dict], image_shape: tuple[int, int]) tuple[list[pyzm.models.detection.Detection], list[pyzm.models.detection.BBox]]

Keep only detections whose bounding box intersects at least one zone.

Parameters:
  • detections – Raw detections from a backend.

  • zones – Each zone is a dict-like with name (str), points (list of (x, y) tuples), and optionally pattern (str | None). These can be pyzm.models.zm.Zone objects turned into dicts via as_dict(), or simple dicts.

  • image_shape(height, width) of the analysed image. If zones is empty a full-image zone is synthesised.

Returns:

  • kept – Detections that intersect at least one zone and whose label matches the zone’s pattern (or the default .*).

  • error_boxes – Bounding boxes of detections that were filtered out.

pyzm.ml.filters.filter_past_per_type(detections: list[Detection], config: DetectorConfig) list[Detection]

Apply past-detection dedup per model-type using config settings.

This is the standalone version of the logic formerly in ModelPipeline._filter_past_per_type().

pyzm.ml.filters.load_past_detections(past_file: str) tuple[list[list[int]], list[str]]

Load (saved_boxes, saved_labels) from a pickle file.

Returns ([], []) on missing file, empty file, or read error.

pyzm.ml.filters.match_past_detections(detections: list[pyzm.models.detection.Detection], saved_boxes: list[list[int]], saved_labels: list[str], max_diff_area: str = '5%', label_area_overrides: dict[str, str] | None = None, ignore_labels: list[str] | None = None, aliases: list[list[str]] | None = None) list[pyzm.models.detection.Detection]

Filter detections against past data. Pure logic, no I/O.

Parameters:
  • saved_boxes – Previously saved detection data (from load_past_detections()).

  • saved_labels – Previously saved detection data (from load_past_detections()).

  • max_diff_area – Default area tolerance, e.g. "5%" or "300px".

  • label_area_overrides – Per-label area tolerance, e.g. {"car": "10%"}.

  • ignore_labels – Labels to skip entirely (always kept, never matched).

  • aliases – Groups of equivalent labels, e.g. [["car","bus","truck"]].

pyzm.ml.filters.save_past_detections(past_file: str, detections: list[pyzm.models.detection.Detection]) None

Save current detections to a pickle file for future comparisons.

Configuration Models

Pydantic v2 models for all pyzm configuration: ZM client settings, detector/model parameters, and stream extraction options.

Typed configuration models for pyzm v2.

All configuration is expressed as Pydantic models with sensible defaults. Typos in field names cause immediate validation errors instead of silent failures.

class pyzm.models.config.DetectorConfig(*args: Any, **kwargs: Any)

Top-level configuration for the pyzm.ml.Detector.

frame_strategy: FrameStrategy = 'most_models'
classmethod from_dict(ml_options: dict[str, Any]) DetectorConfig

Build a DetectorConfig from a nested ml_sequence dict.

This lets YAML configs work directly with the v2 API.

image_path: str = '/var/lib/zmeventnotification/images'
match_past_detections: bool = False
match_strategy: MatchStrategy = 'most'
max_detection_size: str | None = None
monitor_id: str | None = None
past_det_max_diff_area: str = '5%'
pattern: str = '.*'
class pyzm.models.config.FrameStrategy(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)

How to pick the best frame across all analysed frames.

FIRST = 'first'
FIRST_NEW = 'first_new'
MOST = 'most'
MOST_MODELS = 'most_models'
MOST_UNIQUE = 'most_unique'
class pyzm.models.config.MatchStrategy(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)

How to pick results when multiple model variants run for the same type.

FIRST = 'first'
MOST = 'most'
MOST_UNIQUE = 'most_unique'
UNION = 'union'
class pyzm.models.config.ModelConfig(*args: Any, **kwargs: Any)

Configuration for a single ML model/backend in the pipeline.

alpr_key: str | None = None
alpr_service: str | None = None
alpr_url: str | None = None
aws_access_key_id: str | None = None
aws_region: str = 'us-east-1'
aws_secret_access_key: str | None = None
birdnet_lat: float = -1.0
birdnet_lon: float = -1.0
birdnet_min_conf: float = 0.25
birdnet_overlap: float = 0.0
birdnet_sensitivity: float = 1.0
config: str | None = None
disable_locks: bool = False
enabled: bool = True
face_model: str = 'cnn'
face_num_jitters: int = 1
face_recog_dist_threshold: float = 0.6
face_train_model: str = 'cnn'
face_upsample_times: int = 1
framework: ModelFramework = 'opencv'
known_faces_dir: str | None = None
labels: str | None = None
max_detection_size: str | None = None
max_lock_wait: int = 120
max_processes: int = 1
min_confidence: float = 0.3
model_height: int | None = None
model_width: int | None = None
name: str | None = None
pattern: str = '.*'
platerec_min_dscore: float = 0.1
platerec_min_score: float = 0.2
processor: Processor = 'cpu'
save_unknown_faces: bool = False
save_unknown_faces_leeway_pixels: int = 0
type: ModelType = 'object'
unknown_faces_dir: str | None = None
weights: str | None = None
class pyzm.models.config.ModelFramework(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)

Concrete ML backend implementation.

BIRDNET = 'birdnet'
CORAL = 'coral_edgetpu'
FACE_DLIB = 'face_dlib'
FACE_TPU = 'face_tpu'
HOG = 'hog'
OPENALPR = 'openalpr'
OPENCV = 'opencv'
PLATE_RECOGNIZER = 'plate_recognizer'
REKOGNITION = 'aws_rekognition'
VIRELAI = 'virelai'
class pyzm.models.config.ModelType(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)

Kind of ML model in the detection pipeline.

ALPR = 'alpr'
AUDIO = 'audio'
FACE = 'face'
OBJECT = 'object'
class pyzm.models.config.Processor(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)

Hardware target for inference.

CPU = 'cpu'
GPU = 'gpu'
TPU = 'tpu'
class pyzm.models.config.ServerConfig(*args: Any, **kwargs: Any)

Configuration for the pyzm ML detection server (pyzm.serve).

auth_enabled: bool = False
auth_password: pydantic.SecretStr = ''
auth_username: str = 'admin'
base_path: str = '/var/lib/zmeventnotification/models'
detector_config: DetectorConfig | None = None
host: str = '0.0.0.0'
port: int = 5000
processor: Processor = 'cpu'
token_expiry_seconds: int = 3600
token_secret: str = 'change-me'
class pyzm.models.config.StreamConfig(*args: Any, **kwargs: Any)

Controls how frames are extracted from a ZM event or video file.

contig_frames_before_error: int = 5
convert_snapshot_to_fid: bool = False
delay: int = 0
delay_between_frames: int = 0
delay_between_snapshots: int = 0
delete_after_analyze: bool = False
disable_ssl_cert_check: bool = True
download: bool = False
download_dir: str = '/tmp'
frame_skip: int = 1
classmethod from_dict(d: dict[str, Any]) StreamConfig

Build a StreamConfig from a stream_sequence dict.

Handles the string-valued conventions of the YAML config:

  • resize='no'None, resize='800'800

  • frame_set='snapshot,alarm,1'['snapshot', 'alarm', '1']

  • 'yes'/'no' strings → bool

Keys not relevant to StreamConfig (like api, polygons, mid, frame_strategy) are silently ignored.

max_attempts: int = 1
max_frames: int = 0
resize: int | None = None
save_frames: bool = False
save_frames_dir: str = '/tmp'
sleep_between_attempts: int = 3
start_frame: int = 1
class pyzm.models.config.TypeOverrides(*args: Any, **kwargs: Any)

Per-model-type overrides (e.g. under object.general, face.general).

aliases: list[list[str]] | None = None
ignore_past_detection_labels: list[str] | None = None
match_past_detections: bool | None = None
match_strategy: MatchStrategy | None = None
max_detection_size: str | None = None
past_det_max_diff_area: str | None = None
class pyzm.models.config.ZMClientConfig(*args: Any, **kwargs: Any)

Connection settings for ZoneMinder.

api_url: str
basic_auth_password: SecretStr | None = None
basic_auth_user: str | None = None
conf_path: str | None = None
db_host: str | None = None
db_name: str | None = None
db_password: SecretStr | None = None
db_user: str | None = None
password: SecretStr | None = None
portal_url: str | None = None
timeout: int = 30
user: str | None = None
verify_ssl: bool = True

Detection result models.

Results are proper objects instead of parallel arrays - no more index-mismatch bugs from matched_data['labels'][i] / matched_data['boxes'][i].

class pyzm.models.detection.BBox(x1: int, y1: int, x2: int, y2: int)

Axis-aligned bounding box (x1, y1) top-left to (x2, y2) bottom-right.

property area: int
as_list() list[int]
as_polygon_coords() list[tuple[int, int]]

Return four corners suitable for Shapely Polygon().

property center: tuple[int, int]
property height: int
property width: int
x1: int
x2: int
y1: int
y2: int
class pyzm.models.detection.Detection(label: str, confidence: float, bbox: BBox, model_name: str = '', detection_type: str = 'object')

A single detected object/face/plate.

bbox: BBox
confidence: float
detection_type: str
label: str
matches_pattern(pattern: str) bool
model_name: str
class pyzm.models.detection.DetectionResult(detections: list[Detection] = <factory>, frame_id: int | str | None = None, image: np.ndarray | None = None, image_dimensions: dict[str, tuple[int, int] | None] = <factory>, error_boxes: list[BBox] = <factory>)

Aggregate result returned by Detector.detect().

annotate(*, polygons=None, write_conf=True, poly_color=(255, 255, 255), poly_thickness=1, draw_error_boxes=True, **draw_kwargs) np.ndarray

Draw bounding boxes on self.image and return the annotated copy.

Parameters:
  • polygons (list[dict] | None) – Zone polygons to draw. Each dict must have a 'value' key containing a sequence of (x, y) coordinate pairs.

  • write_conf (bool) – If True (default), append confidence% to each label.

  • poly_color (tuple[int, int, int]) – BGR colour for polygon outlines (default white).

  • poly_thickness (int) – Line thickness for polygon outlines (default 1).

  • draw_error_boxes (bool) – If True (default), draw red rectangles for error boxes.

property boxes: list[list[int]]
property confidences: list[float]
detections: list[Detection]
error_boxes: list[BBox]
filter_by_pattern(pattern: str) DetectionResult

Return a new result keeping only detections whose label matches pattern.

frame_id: int | str | None = None
classmethod from_dict(data: dict) DetectionResult

Reconstruct a DetectionResult from a wire dict (inverse of to_dict).

The image field is left as None — the caller already has the original image and can attach it after reconstruction.

image: np.ndarray | None = None
image_dimensions: dict[str, tuple[int, int] | None]
property labels: list[str]
property matched: bool
property summary: str

Human-readable one-liner, e.g. person:97% car:85%.

to_dict() dict

Return data in the matched_data dict format.

ZoneMinder data models.

Typed representations of ZM resources (monitors, events, frames, zones). Models carry a _client back-reference so that resource operations (arm, disarm, zones, frames, etc.) are methods on the object itself.

class pyzm.models.zm.Event(id: int, name: str = '', monitor_id: int = 0, cause: str = '', notes: str = '', start_time: datetime | None = None, end_time: datetime | None = None, length: float = 0.0, frames: int = 0, alarm_frames: int = 0, max_score: int = 0, max_score_frame_id: int | None = None, storage_path: str = '', _raw: dict = <factory>, _client: ZMClient | None = None)

A ZoneMinder event.

When retrieved via ZMClient, the event carries a back-reference so that resource operations are available as methods:

ev = zm.event(12345)
ev.update_notes("person detected")
ev.tag(["person", "car"])
ev.delete()
alarm_frames: int = 0
cause: str = ''
delete() None

Delete this event.

end_time: datetime | None = None
extract_frames(stream_config: StreamConfig | None = None) tuple[list[tuple[int | str, Any]], dict[str, tuple[int, int] | None]]

Extract frames from this event as numpy arrays.

frames: int = 0
classmethod from_api_dict(data: dict, client: ZMClient | None = None) Event

Build from a ZM API Event JSON dict.

get_frames() list[pyzm.models.zm.Frame]

Fetch per-frame metadata (Score, Type, Delta) for this event.

id: int
length: float = 0.0
max_score: int = 0
max_score_frame_id: int | None = None
monitor_id: int = 0
name: str = ''
notes: str = ''
path() str | None

Construct the filesystem path for this event.

raw() dict

Return the full, unmodified API response dict.

save_objdetect(image, metadata: dict, path_override: str | None = None) str | None

Write objdetect.jpg and objects.json to the event directory.

Returns the directory path used, or None if no path was available.

start_time: datetime | None = None
storage_path: str = ''
tag(labels: list[str]) None

Tag this event with detected object labels.

update_notes(notes: str) None

Update the Notes field of this event.

class pyzm.models.zm.Frame(frame_id: int | str, event_id: int, type: str = '', score: int = 0, delta: float = 0.0, _raw: dict = <factory>)

Metadata for a single event frame.

delta: float = 0.0
event_id: int
frame_id: int | str
raw() dict

Return the full, unmodified API response dict.

score: int = 0
type: str = ''
class pyzm.models.zm.Monitor(id: int, name: str = '', function: str = '', enabled: bool = True, width: int = 0, height: int = 0, type: str = '', controllable: bool = False, control_id: int | None = None, zones: list[Zone] = <factory>, status: MonitorStatus = <factory>, _raw: dict = <factory>, _client: ZMClient | None = None, _ptz_caps_cache: PTZCapabilities | None = None)

A ZoneMinder monitor.

When retrieved via ZMClient, the monitor carries a back-reference so that resource operations are available as methods:

m = zm.monitor(1)
m.zones()
m.arm()
m.update(Function="Modect")
alarm_status() dict

Get alarm status for this monitor.

arm() dict

Trigger alarm ON for this monitor.

control_id: int | None = None
controllable: bool = False
daemon_status() dict

Get daemon status for this monitor’s capture daemon (zmc).

delete_events(**kwargs) int

Bulk-delete events scoped to this monitor.

Accepts the same filter params as ZMClient.delete_events(): before, min_alarm_frames, limit.

disarm() dict

Cancel alarm for this monitor.

enabled: bool = True
events(**kwargs) list[pyzm.models.zm.Event]

Query events scoped to this monitor.

Accepts the same filter params as ZMClient.events(): since, until, min_alarm_frames, object_only, limit.

classmethod from_api_dict(data: dict, client: ZMClient | None = None) Monitor

Build from a ZM API Monitor JSON dict.

function: str = ''
get_zones() list[pyzm.models.zm.Zone]

Fetch detection zones for this monitor from the ZM API.

height: int = 0
id: int
name: str = ''
ptz(command: str, *, mode: str = 'auto', preset: int = 1, stop_after: float | None = None) None

Send a PTZ command to this monitor.

Parameters:
  • command – Direction or action: "up", "down", "left", "right", "up_left", "up_right", "down_left", "down_right", "zoom_in", "zoom_out", "stop", "home", "preset".

  • mode – Movement mode — "auto" (default, picks the best mode supported by the camera’s control profile), "con" (continuous), "rel" (relative), or "abs" (absolute).

  • preset – Preset number for command="preset" (default 1).

  • stop_after – If set, automatically send a "stop" command after this many seconds. Blocks for the duration.

ptz_capabilities() PTZCapabilities

Fetch PTZ capabilities for this monitor’s control profile.

raw() dict

Return the full, unmodified API response dict.

snapshot_url(**kwargs) str

Return a single-frame snapshot URL for this monitor.

Parameters:

**kwargs – Extra query params forwarded to the URL (e.g. scale=50).

status: MonitorStatus
streaming_url(protocol: str = 'mjpeg', **kwargs) str

Return a streaming URL for this monitor.

Parameters:
  • protocol – Streaming protocol. Currently only "mjpeg" is supported.

  • **kwargs – Extra query params forwarded to the URL (e.g. maxfps=5, scale=50).

type: str = ''
update(**kwargs) None

Update monitor fields (Function, Enabled, Name, etc.).

width: int = 0
zones: list[Zone]
class pyzm.models.zm.MonitorStatus(state: str = '', fps: float = 0.0, analysis_fps: float = 0.0, bandwidth: int = 0, capturing: str = 'None', _raw: dict = <factory>)

Runtime status of a monitor.

analysis_fps: float = 0.0
bandwidth: int = 0
capturing: str = 'None'
fps: float = 0.0
raw() dict

Return the full, unmodified API response dict.

state: str = ''
class pyzm.models.zm.Notification(id: int, user_id: int = 0, token: str = '', platform: str = '', monitor_list: str | None = None, interval: int = 0, push_state: str = 'enabled', app_version: str | None = None, profile: str | None = None, badge_count: int = 0, last_notified_at: datetime | None = None, _raw: dict = <factory>, _client: ZMClient | None = None)

A ZoneMinder push notification token registration.

app_version: str | None = None
badge_count: int = 0
delete() None
classmethod from_api_dict(data: dict, client: ZMClient | None = None) Notification

Build from a ZM API Notification JSON dict.

id: int
interval: int = 0
is_throttled() bool

Check if this token is currently throttled.

last_notified_at: datetime | None = None
monitor_list: str | None = None
monitors() list[int] | None

Parse MonitorList into list of ints. None = all monitors.

platform: str = ''
profile: str | None = None
push_state: str = 'enabled'
raw() dict
should_notify(monitor_id: int) bool

Check if this token should receive notifications for the given monitor.

token: str = ''
update_last_sent(badge: int | None = None) None

Update LastNotifiedAt to now and optionally set BadgeCount.

user_id: int = 0
class pyzm.models.zm.PTZCapabilities(can_move: bool = False, can_move_con: bool = False, can_move_rel: bool = False, can_move_abs: bool = False, can_move_diag: bool = False, can_zoom: bool = False, can_zoom_con: bool = False, can_zoom_rel: bool = False, can_zoom_abs: bool = False, can_pan: bool = False, can_tilt: bool = False, has_presets: bool = False, num_presets: int = 0, has_home_preset: bool = False, can_set_presets: bool = False, can_reset: bool = False, _raw: dict = <factory>)

PTZ control profile capabilities.

can_move: bool = False
can_move_abs: bool = False
can_move_con: bool = False
can_move_diag: bool = False
can_move_rel: bool = False
can_pan: bool = False
can_reset: bool = False
can_set_presets: bool = False
can_tilt: bool = False
can_zoom: bool = False
can_zoom_abs: bool = False
can_zoom_con: bool = False
can_zoom_rel: bool = False
classmethod from_api_dict(data: dict) PTZCapabilities

Build from a ZM API Control JSON dict.

has_home_preset: bool = False
has_presets: bool = False
num_presets: int = 0
raw() dict

Return the full, unmodified API response dict.

class pyzm.models.zm.Zone(name: str, points: list[tuple[float, float]], pattern: str | None = None, ignore_pattern: str | None = None, _raw: dict = <factory>)

A detection zone polygon belonging to a monitor.

as_dict() dict
ignore_pattern: str | None = None
name: str
pattern: str | None = None
points: list[tuple[float, float]]
raw() dict

Return the full, unmodified API response dict.

Remote ML Detection Server

A FastAPI-based server that loads models once and serves detection requests over HTTP. See the serve guide for usage.

FastAPI application factory for the pyzm ML detection server.

pyzm.serve.app.create_app(config: ServerConfig | None = None) fastapi.FastAPI

Build and return a configured FastAPI application.

The Detector is created during the lifespan startup phase so models are loaded once and persist across requests.

JWT authentication for the pyzm serve API.

pyzm.serve.auth.create_login_route(config: ServerConfig)

Return a login endpoint handler bound to config.

When config.auth_enabled is False the route accepts any credentials so that clients with ml_user / ml_password configured don’t fail with a 404.

pyzm.serve.auth.create_token_dependency(config: ServerConfig)

Return a FastAPI dependency that verifies Bearer tokens.