IP cameras and NVRs over ONVIF for Jennifer
programs: WS-Discovery on the LAN, device information and clock, media
profiles with RTSP and snapshot URLs, snapshots, PTZ moves and presets,
brightness and focus, and events from a pull-point subscription. One client
for every vendor that implements the ONVIF profiles; call reaches any
other operation.
jvc add @deckyard/onvifThis vendors the deck to vendor/<scope>/onvif/. Without jvc, unpack the
src/ subtree of a release archive (<scope>-onvif-<version>.tar.gz) there.
import "@deckyard/onvif/" as onvif;
for (def d in onvif.discover(3000)) {
io.printf("%s %s\n", $d.address, $d.name);
}
def cam as onvif.Device init onvif.connect(
onvif.device("http://192.168.1.64/onvif/device_service", "admin", $password));
for (def p in onvif.profiles($cam)) {
io.printf("%s %s\n", $p.name, onvif.streamUri($cam, $p.token).address);
}
fs.writeBytes("snapshot.jpg", onvif.snapshot($cam, onvif.profiles($cam)[0].token));
Requests are signed with a WS-Security PasswordDigest built per request on
the camera's clock (the password never travels); HTTP Digest is answered
where a camera asks for it. Plain http is allowed to private, link-local
and loopback addresses; anything else needs withPlainHttp.
Jennifer 0.26.0 or newer and the capability net (SOAP over HTTP, WS-Discovery
over UDP), so not jennifer-tiny. No other deck. RTSP and video decoding are
out of scope: the deck returns the stream URLs.
tools/check-all.sh # the full gate, as CI runs it
jennifer test src/onvif_test.j # the test overlay (a loopback fake camera)
tools/check-docs.sh # docs against the code
tools/pack.sh dist # build the release archiveLGPL-3.0-only, see LICENSE. Copyright (C) 2026 mplx <mplx@jennifer-lang.dev>.