v0.2 · vendor-neutral VMS

A VMS that follows the suspect, not the vendor.

OpenEye is an open, lightweight video management system for any ONVIF / RTSP camera. Pursuit-mode multi-cam, AI triage queue, forensic search, signed case manifests - and a companion Linux agent (Node + ffmpeg) that handles ONVIF discovery and RTSP-to-HLS transmuxing for you.

Cameras
Any ONVIF / RTSP
Host
1 Linux box
Discovery
ONVIF (via agent)
Streaming
LL-HLS (via agent)
The console in action

See what operators actually use.

Real captures from the console against the bundled demo dataset - not mockups.

openeye · /live
Pursuit view - OpenEye console screenshot

Spatial follow-the-suspect mode. Focus camera at the center, the 8 nearest cameras ringed around it by compass direction. Click any neighbor to recenter; the layout re-pivots in place.

What's shipped today

Everything an operator needs to run a shift.

All of this works against the bundled demo data - no hardware required to evaluate the UX.

Pursuit view - spatial multi-cam

Follow a suspect across the camera mesh. Focus camera at center, the 8 nearest cameras placed by compass direction from your floor plan. Click any neighbor to recenter; Smart Handoff highlights the neighbor where fresh person/vehicle detections fire.

Live workspace v2

1×1 / 2×2 / 3×3 / 4×4 grids, multi-select picker, saved views, sync play/pause/mute, per-tile digital zoom + pan, fullscreen, full hotkey set.

AI triage queue

Severity-coded inbox with rationale, confidence, signal tag and zone. Acknowledge / escalate / resolve / dismiss in one key. Escalation fires notifications automatically.

Forensic search

Scrub the archive by object class, color, time, site and confidence. Thumbnails carry overlaid bounding boxes and deep-link straight to the moment in playback.

Investigations & evidence

Bundle clips, events and detections into cases. Generate signed JSON manifests with a deterministic SHA-256 over the case contents for chain of custody (video bundling is on the roadmap).

Playback & timeline

24-hour scrubber with variable speed, recording-segment markers, and bookmark-to-case for the current frame range.

Maps & floor plans

Upload a floor plan per building level, calibrate scale, place each camera with yaw and FOV cone. The same geometry powers Pursuit's neighbor solver.

ONVIF discovery + onboarding

Guided 4-step wizard: pick a site, WS-Discovery scan on the LAN (run by the agent), vendor URL templates (Axis/Hikvision/Dahua/Reolink), ffprobe validation, save.

Built-in HLS streaming

The agent transmuxes each RTSP stream to LL-HLS on demand and signs short-lived URLs (HMAC). No separate re-streamer required for the live grid.

PTZ control

ONVIF ContinuousMove and preset recall from the live tile overlay; agent forwards commands to the camera.

Access control

Door registry plus an ingest endpoint (HID/Brivo/etc.) - badge events appear in the live access dashboard next to camera events.

Notifications fan-out

Per-channel routing for webhook / Slack (email channel is queued, no SMTP relay shipped). Severity thresholds, delivery log, and a manual dispatch button per triage event.

System health

Per-agent liveness, per-camera FPS / bitrate / codec, recording storage and disk usage - at a glance, with red/amber/green thresholds.

Policy catalog

11 signals (Intrusion, Tailgating, Loitering, PPE, Crowd, Weapon, Vehicle-in-zone…) with admin enable/disable toggles, ready to be evaluated by your inference worker.

Auth, roles, RLS

Email + Google sign-in. First user is admin; everyone else is viewer. Per-camera ACLs and row-level security on every table.

Operator chrome

Collapsible sidebar grouped by Monitor / Investigate / Manage / System, command palette (⌘K), live status bar, light/dark themes, mobile view.

Out of scope (on purpose)

Honest scope.

These are deliberately delegated to open-source projects that already do them well. Wiring is documented in Setup.

It is not a continuous NVR

The agent can register clips and the console plays them back, but there is no built-in 24/7 disk writer, retention enforcer, or storage pool manager.

Use instead: Frigate or MediaMTX for continuous recording + retention; OpenEye indexes the resulting clips.

It does not run AI inference itself

Triage, search and policy signals are surfaced beautifully, but actual object/person/vehicle detection happens in an external worker that posts to /api/public/v1/detections and /events.

Use instead: Frigate (with Coral/CPU/GPU), CodeProject.AI, or a custom YOLO worker that hits OpenEye's ingest endpoints.

It does not do two-way audio

PTZ is supported; backchannel audio and intercom are not.

Use instead: go2rtc handles ONVIF backchannel today if you need it.

It does not enforce policy server-side

Toggling a policy in the UI marks it active, but evaluation against a live stream is the inference worker's job - OpenEye just stores the resulting events with their policy id.

Use instead: Frigate zones/objects or a custom analytics worker tagged with the policy id.

Prerequisites

What you need to bring.

For the quick start you only need a Linux box and our agent - that's enough to discover cameras, stream live, and run AI triage on seeded events. The other items are optional upgrades when you want continuous recording or your own inference pipeline.

Linux host

small server / NUC / mini-PC

One box to run the agent (and optionally Frigate). 4 cores + 8 GB RAM handles ~16 cameras of transmuxing; add a Coral USB or a small GPU only if you also want detection.

OpenEye agent

required - bundled, single binary

Handles ONVIF WS-Discovery, ffprobe-based camera validation, RTSP→LL-HLS transmuxing with signed URLs, PTZ commands, snapshots and heartbeats. Generate a token and grab the docker-compose from /setup.

Recording / NVR

optional - only if you need long retention

OpenEye indexes recordings (POST /api/public/v1/recordings) and plays them back, but does not write continuous 24/7 video to disk. Pair with an NVR for retention.

AI / object detection

optional - powers real (not seeded) triage

If you want the triage queue, forensic search and policies to fire from real video, run an inference worker that POSTs to /api/public/v1/events and /detections.

Reverse proxy + TLS

needed once you expose anything off the LAN

Modern browsers require HTTPS for camera/microphone, WebRTC and Service Workers. The generated Caddyfile terminates TLS in front of the agent and console.

Quick start

From zero to live grid in 10 minutes.

The minimum useful setup is: OpenEye console + the OpenEye agent on a Linux host. The agent discovers your cameras over ONVIF and transmuxes RTSP to LL-HLS that the browser can play directly.

  1. 1

    Sign up and seed demo data

    Open the console and create an account - the first user automatically becomes admin. Use the Demo menu in the top bar to seed sites, cameras, events and detections so every screen has data to show. No hardware required.
  2. 2

    Generate an agent token in /setup

    Open Setup, click New token, then download the generated docker-compose.yml and Caddyfile. The bundle wires the agent to your console with a signed bearer token.
    # On your Linux box
    mkdir -p /opt/openeye && cd /opt/openeye
    # (drop the docker-compose.yml + Caddyfile downloaded from /setup here)
    docker compose up -d
    docker compose logs -f agent
  3. 3

    Discover cameras on the LAN

    In Cameras → Add camera, the wizard runs ONVIF WS-Discovery through your agent and lists every device it found. Pick one, enter credentials, and the agent's GetProfiles / GetStreamUri calls auto-fill the RTSP URL. ffprobe validates the stream before you save.
  4. 4

    (Optional) Add Frigate for continuous recording + detection

    Frigate happily consumes the same RTSP URLs and posts events back to OpenEye. The /setup page also generates a starter frigate-config.yml and a docker-compose service for it.
    # docker-compose.yml fragment (also generated for you in /setup)
    services:
      frigate:
        image: ghcr.io/blakeblackshear/frigate:stable
        devices: ["/dev/bus/usb:/dev/bus/usb"]   # Coral
        environment:
          OPENEYE_URL: https://vms.example.com
          OPENEYE_TOKEN: ${AGENT_TOKEN}
        volumes:
          - ./frigate-config.yml:/config/config.yml:ro
          - /mnt/nvr:/media/frigate
  5. 5

    Wire notifications

    In Notificationsadd a Slack incoming webhook or a generic HTTPS webhook with a severity threshold. Escalating any triage event will fan out to every matching channel with a delivery log.
  6. 6

    Put it behind TLS

    The generated Caddyfile already terminates HTTPS at vms.example.com and proxies the agent + console. WebRTC and modern browsers require it.
    # Caddyfile (auto-generated)
    vms.example.com {
      reverse_proxy /api/agent/* agent:8787
      reverse_proxy /hls/*       agent:8787
      reverse_proxy *            openeye:8080
    }

Reference architecture

  ┌─────────────────────┐  ONVIF/RTSP   ┌───────────────────────┐  LL-HLS  ┌───────────────────┐
  │  IP cameras (any)   │ ────────────► │  OpenEye agent     │ ───────► │ OpenEye console│
  │  Axis · Hikvision   │  WS-Discovery │  (single Go/Node bin) │  signed  │ (this app)        │
  │  Dahua · Reolink    │ ◄──── PTZ ─── │  ffmpeg transmux      │  URLs    └─────────┬─────────┘
  └─────────────────────┘               └─────────┬─────────────┘                    │
                                                  │ webhook                          ▼
                                ┌──────────────── ┼ ─────────────┐         ┌───────────────────┐
                                │   Optional add-ons              │ events  │  Notifications    │
                                │   Frigate (NVR + AI)            │ ──────► │  Slack/webhook    │
                                │   go2rtc, MediaMTX (re-stream)  │         └───────────────────┘
                                └─────────────────────────────────┘

Everything inside the agent box ships with OpenEye. Frigate / go2rtc / MediaMTX are optional drop-ins when you want continuous NVR recording or your own inference pipeline.

Ready to look around?

The console runs against demo data out of the box - first user becomes admin. Wire a real camera whenever you're ready.

Open the console