Ask your CCTV system anything in plain English.
cctvQL is an open-source conversational query layer for CCTV and surveillance systems. It wraps any camera system — Frigate, ONVIF, Hikvision, Dahua and more — with a natural language interface powered by local or cloud LLMs.
Quick Start · Documentation · Adapters · Contributing · Sponsor
you > Was there anyone near the front door last night?
cctvQL > Yes — 3 person detections on "Front Door" between 22:14 and 23:47.
• 22:14 — person (96%) in zone: driveway
• 23:02 — person (88%)
• 23:47 — person (91%) in zone: porch
you > Show me the clip from 23:47
cctvQL > Clip from Front Door (23:47–23:49, 112s):
http://192.168.1.100:5000/api/events/abc123/clip.mp4
No dashboards. No complex queries. Just ask.
- Natural language queries — ask about events, cameras, clips, and system health in plain English
- Multi-turn conversations — context-aware follow-up questions backed by SQLite session persistence
- Vendor-agnostic — adapter pattern supports any CCTV system; ships with Frigate, ONVIF, Hikvision, Dahua
- Pluggable LLM backends — Ollama (local/private), OpenAI, Anthropic, or any OpenAI-compatible API
- REST API — integrate with Home Assistant, Grafana, custom dashboards, or mobile apps
- PTZ control — pan, tilt, zoom and preset recall via REST API for supported cameras
- Event export — download event history as CSV or JSON from
/events/export - Alert rules — create rules to notify when specific labels appear on specific cameras in time windows
- Multi-channel notifications — Telegram, Slack, ntfy, email, and webhook; all fire concurrently
- WebSocket streaming — real-time event push to any connected client via
ws://host/ws/events - Prometheus metrics —
/metricsendpoint for Grafana, alerting, and observability - Camera health monitoring — background poller tracks per-camera online/offline status
- Optional API key auth — protect your endpoint with
CCTVQL_API_KEYenv var - Multi-tenant support — JWT auth, per-user camera permissions, admin user management (
CCTVQL_MULTI_TENANT=1) - Anomaly detection — statistical spike/silence detection per camera; ask "anything unusual today?"
- Demo adapter — try cctvQL without any hardware; realistic mock data built-in
- Interactive CLI — terminal-based conversational REPL
- Real-time events — MQTT subscription for live alerts (Frigate)
- Home Assistant integration — native custom integration with sensors, binary sensors (per-camera motion), PTZ and query services; installable via HACS
- Docker-ready — running in under 5 minutes with persistent SQLite storage
pip install cctvql
cctvql chat --adapter demo --llm ollamaThe demo adapter ships with 4 cameras, 20 realistic events, and 5 clips — no Frigate or ONVIF device required. Use it to explore the query interface, build integrations, or write tests.
# 1. Clone and configure
git clone https://github.com/arunrajiah/cctvql.git
cd cctvql
cp config/example.yaml config/config.yaml
# 2. Edit config/config.yaml with your Frigate URL and LLM settings
nano config/config.yaml
# 3. Start
docker compose up -d
# 4. Try it
curl -X POST http://localhost:8000/query \
-H "Content-Type: application/json" \
-d '{"query": "Show me all cameras"}'API docs: http://localhost:8000/docs
pip install cctvql
# Interactive chat
cctvql chat --config config/config.yaml
# REST API server
cctvql serve --config config/config.yaml --port 8000git clone https://github.com/arunrajiah/cctvql.git
cd cctvql
pip install -e ".[dev,mqtt,onvif]"
cctvql chat| Topic | Link |
|---|---|
| Configuration reference | docs/configuration.md |
| REST API reference | docs/api.md |
| Notifications setup | docs/notifications.md |
| Session & event persistence | docs/persistence.md |
| Writing an adapter | docs/adapters.md |
| LLM backend setup | docs/llm-backends.md |
| Home Assistant integration | docs/home-assistant.md |
| Docker deployment | docs/docker.md |
| Frigate + cctvQL sidecar | deploy/frigate-sidecar/ |
| Troubleshooting | docs/troubleshooting.md |
| System | Type | Adapter | Status |
|---|---|---|---|
| Frigate NVR | NVR | frigate |
✅ Full support (REST + MQTT) — sidecar template |
| Any ONVIF camera/NVR | Camera/NVR | onvif |
✅ Full support |
| Demo / Mock | Built-in | demo |
✅ No hardware needed — try cctvQL now |
| Hikvision | NVR/Camera | hikvision |
✅ Full support (ISAPI) |
| Dahua | NVR/Camera | dahua |
✅ Full support (CGI) |
| Synology Surveillance Station | NVR | synology |
✅ Full support (Web API) |
| Milestone XProtect | Enterprise NVR | milestone |
✅ Full support (REST API) |
| Scrypted | Smart home NVR | scrypted |
✅ Full support (Bearer token) |
| Your system | Any | — | Write an adapter! |
Writing an adapter takes ~100 lines. See the adapter guide.
| Backend | Privacy | Cost | Quality |
|---|---|---|---|
| Ollama (llama3, mistral, phi3…) | 🔒 100% local | Free | ⭐⭐⭐⭐ |
| OpenAI (gpt-4o-mini, gpt-4o) | ☁️ Cloud | Paid | ⭐⭐⭐⭐⭐ |
| Anthropic (claude-haiku, claude-sonnet) | ☁️ Cloud | Paid | ⭐⭐⭐⭐⭐ |
| LM Studio | 🔒 Local | Free | ⭐⭐⭐⭐ |
| Any OpenAI-compatible API | Varies | Varies | Varies |
Recommended for privacy: Ollama with llama3 or mistral. No data leaves your network.
# config/config.yaml
llm:
active: ollama
backends:
ollama:
provider: ollama
host: http://localhost:11434
model: llama3
adapters:
active: frigate
systems:
frigate:
type: frigate
host: http://192.168.1.100:5000
mqtt_host: 192.168.1.100 # optional, for real-time events
# Optional: alert notifications (any combination of channels)
notifications:
telegram:
bot_token: "123456:ABC-DEF..."
chat_id: "-1001234567890"
slack:
webhook_url: "https://hooks.slack.com/services/..."
ntfy:
topic: my-cctvql-alertsDatabase path (conversation history + event log):
export CCTVQL_DB_PATH=/data/cctvql.dbSee docs/configuration.md for the full reference.
Notification channels: docs/notifications.md.
Session persistence: docs/persistence.md.
# Natural language query (multi-turn, session history persisted to SQLite)
POST /query
{"query": "Did anyone come to the front door after midnight?", "session_id": "my-session"}
# List cameras
GET /cameras
# Get events with filters
GET /events?camera=driveway&label=person&limit=10
# Export events as CSV or JSON
GET /events/export
GET /events/export?fmt=json&camera=Front+Door&label=person
# PTZ control (pan/tilt/zoom)
POST /cameras/{camera_id}/ptz
{"action": "left", "speed": 50}
# PTZ presets
GET /cameras/{camera_id}/ptz/presets
# System health
GET /health
# Per-camera health status
GET /health/cameras
# Alert rules (CRUD)
GET /alerts
POST /alerts
GET /alerts/{id}
PATCH /alerts/{id}
DELETE /alerts/{id}
# Anomaly detection (statistical spike/silence analysis)
GET /anomalies
GET /anomalies?hours=6&camera=Front+Door&threshold=1.5
# Prometheus metrics (for Grafana / alerting)
GET /metrics
# Clear conversation session (also removes from database)
DELETE /sessions/{session_id}Real-time event streaming via WebSocket:
ws://localhost:8000/ws/events
Optional API key auth — set CCTVQL_API_KEY env var to require X-API-Key header on all requests.
Interactive Swagger docs available at http://localhost:8000/docs.
See docs/api.md for full endpoint documentation.
┌─────────────────────────────────────────────────────────┐
│ User Interface │
│ CLI Chat │ REST API │ Home Assistant │
└─────────────────────────┬───────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────┐
│ NLP Engine │
│ Natural Language → QueryContext (intent + params) │
└─────────────────────────┬───────────────────────────────┘
│
┌──────────────▼──────────────┐
│ LLM Registry │
│ Ollama │ OpenAI │ Anthropic │
└──────────────┬──────────────┘
│
┌─────────────────────────▼───────────────────────────────┐
│ Query Router │
│ Routes intent to adapter → formats human response │
└─────────────────────────┬───────────────────────────────┘
│
┌──────────────▼──────────────┐
│ Adapter Registry │
│ Frigate │ ONVIF │ Your NVR │
└──────────────┬──────────────┘
│
┌─────────────────────────▼───────────────────────────────┐
│ Your CCTV System │
│ NVR / IP Cameras / Recording Storage │
└─────────────────────────────────────────────────────────┘
cctvql/
├── cctvql/
│ ├── core/
│ │ ├── schema.py # Vendor-agnostic data models
│ │ ├── nlp_engine.py # Natural language → QueryContext
│ │ └── query_router.py # QueryContext → adapter → response
│ ├── adapters/
│ │ ├── base.py # BaseAdapter interface (implement to add a system)
│ │ ├── frigate.py # Frigate NVR (REST + MQTT)
│ │ └── onvif.py # Generic ONVIF adapter
│ ├── llm/
│ │ ├── base.py # BaseLLM interface + LLMRegistry
│ │ ├── ollama_backend.py # Local LLM via Ollama
│ │ ├── openai_backend.py # OpenAI / compatible APIs
│ │ └── anthropic_backend.py
│ ├── interfaces/
│ │ ├── cli.py # Interactive terminal chat
│ │ └── rest_api.py # FastAPI REST server
│ ├── _bootstrap.py # Config loader and wiring
│ └── __main__.py # Entry point (cctvql chat / serve)
├── config/
│ └── example.yaml # Annotated config reference
├── docs/ # Full documentation
├── tests/
├── Dockerfile
├── docker-compose.yml
└── pyproject.toml
Contributions are what make cctvQL useful for everyone. The single highest-impact contribution is writing an adapter for a CCTV system you already have.
git clone https://github.com/arunrajiah/cctvql.git
cd cctvql
pip install -e ".[dev,mqtt,onvif]"
pytest tests/ # all tests should passSee CONTRIBUTING.md for the full guide.
# Common developer commands
make dev # install with all extras + pre-commit hooks
make test # run test suite
make coverage # tests + coverage report
make lint # ruff linter
make type-check # mypy
make demo # interactive demo (no real hardware needed)Most wanted contributions:
- Hikvision adapter
- Dahua adapter
- Synology Surveillance Station adapter
- Vision-based event description (send snapshot to LLM)
- Home Assistant custom integration
Does my camera data leave my network? Only if you use a cloud LLM backend (OpenAI, Anthropic). With Ollama, everything — including the AI processing — stays on your local machine.
Which Frigate version is supported? Frigate 0.12+ is tested. Most features work with 0.9+.
Can I use this with any ONVIF camera? Yes. ONVIF Profile S is supported for live streaming and snapshots. Profile G adds recording/clip support.
Can I query multiple camera systems at once?
Yes — use "multi": true in your query request to fan out across all registered adapters simultaneously.
Is there a Home Assistant integration? A native Home Assistant custom integration is planned. For now, use the REST API endpoint from HA automations.
- Vision analysis — pass event snapshots to multimodal LLMs for rich descriptions
- Hikvision adapter
- Dahua adapter
- Web UI (lightweight chat interface)
- Multi-system queries (query across multiple NVRs simultaneously)
- Alert rules via natural language ("notify me when a person enters Zone A after 10pm")
- Voice interface (Whisper STT + TTS output)
- Multi-channel notifications — Telegram, Slack, ntfy, email, webhook
- PTZ control via REST API (pan, tilt, zoom, presets)
- Session persistence — conversation history survives server restarts (SQLite)
- Event log — every detection written to SQLite; exportable as CSV/JSON
- Camera health monitoring — per-camera online/offline status with background polling
- Prometheus metrics — cameras online/offline, alert rule count
- Home Assistant custom integration — sensors, binary sensors, services, HACS-ready
- ONVIF discovery — auto-detect cameras on the local network (
cctvql discoverCLI +GET /discover/onvif) - Event timeline UI — visual heatmap timeline at
/timelinewith camera rows, time buckets, tooltips, auto-refresh - Face recognition — identify known individuals across camera feeds
- Anomaly detection — statistical spike/silence detection per camera with z-score baseline (
GET /anomalies) - Multi-tenant support — JWT auth, per-user camera permissions, admin user management (
CCTVQL_MULTI_TENANT=1) - Mobile app (React Native)
cctvQL is free and open-source. If it saves you time or powers something you care about, consider sponsoring — it helps keep the project maintained and moving forward.
MIT © 2026 arunrajiah
See LICENSE for the full text.




