Web-based surveillance video player for Alta/Ava Security camera exports with multi-camera sync, timeline, digital zoom, motion analytics, and cryptographic integrity verification. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
7.8 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Alta Video Player (WebAVP) — a web-based surveillance video player for Alta/Ava Security camera exports. Users drag-drop video files or ZIP archives (including AES-256 encrypted ones) and get a multi-camera synchronized playback experience with timeline, digital zoom, cryptographic integrity verification, and automatic motion analytics.
Running the App
python3 app.py
# Serves on http://0.0.0.0:5152
No build step. No dependencies needed at runtime — app.py uses only Python stdlib (http.server). The requirements.txt (flask, requests, gunicorn) is vestigial; the server was rewritten to pure stdlib.
Architecture
Backend (app.py)
Minimal HTTP server with three routes:
GET /— servestemplates/index.htmlGET /static/*— serves static files with path traversal protectionGET /api/verify-cert?serial=...&certificateHash=...— proxies certificate verification tohttps://aware.avasecurity.com/api/v1/public/verifyServerCertificate
Frontend (templates/index.html)
Single self-contained HTML file (~5000 lines) with inline CSS and JavaScript in an IIFE. This is the entire application — there is no framework, no build system, no separate JS modules.
State model: Global channels Map keyed by channel name. Each channel holds segments (video blobs + time ranges), metadata, DOM references, color, and zoom state. Global timeline state (globalStart, globalEnd, currentTime) synchronizes all cameras. Motion analytics state lives in the motionState object.
Key subsystems:
- File ingestion — drag-drop files/folders, ZIP extraction (JSZip for plain, custom AES-256-CTR for encrypted), metadata pairing by base filename, concurrent import guard (
isImportingflag) - Multi-camera grid — responsive CSS grid (1–9 cameras), drag-to-reorder, click-to-expand
- Playback engine —
requestAnimationFrametick loop, per-channel segment visibility management, variable speed (0.25x–8x), frame stepping - Timeline — interactive scrub bar with zoom (mouse wheel), minimap, per-channel segment indicators with color-coded dots, motion heatmap row
- Digital zoom — per-camera scroll-to-zoom (up to 10x) with click-drag panning
- Magnifier tool — draw rectangle to zoom into region
- Slideshow mode — animated grid showing only currently active feeds with transitions
- Motion analytics — automatic background motion detection on load (see below)
- Integrity verification — offline X.509 certificate parsing, RSASSA-PKCS1-v1_5 and ECDSA signature verification via Web Crypto API, optional cloud verification through
/api/verify-cert - Session persistence — IndexedDB caching of video blobs and metadata for page refresh survival
External dependency: /static/jszip.min.js (vendored, for unencrypted ZIP parsing).
Motion Analytics Subsystem
Zero-dependency canvas-based motion detection that runs automatically when videos are loaded.
How it works
- Auto-scan on load —
scheduleAutoScan()is called fromflushPending()andloadSessionData()after videos finish loading. Debounced 800ms to batch all segments. - Dedicated scan videos — scan creates temporary offscreen
<video>elements per segment (withpreload='auto'for fast seeking) that don't interfere with the playback engine. Each is destroyed after its segment is processed. - Pixel-diff at 160x120 — frames are drawn to a small offscreen canvas. Each pixel's RGB delta is compared against a threshold. Changed pixels are counted and mapped to a 10x8 hotspot grid.
- Sensitivity slider (1–100) — maps to two internal thresholds via
sensitivityToThresholds():pixelThreshold(how different a pixel must be) andchangeThreshold(what % of pixels must change). Presets: Indoor (30), Default (40), Parking (45), Outdoor (55). - Motion clusters — consecutive motion detections within
clusterGap(5s) are merged into clusters with start/end times, peak change %, and hotspot data. - Results visualization — motion heatmap row on timeline (purple/red intensity), clickable cluster markers, scrollable event cards in the analytics panel with hotspot mini-grids.
Key state: motionState
sensitivity(1–100),scanInterval(2s default),clusterGap(5s)detector.canvas/detector.ctx— 160x120 offscreen canvas for pixel comparisondetector.prevFrames— Map of channelName → previous ImageDatamotionProfiles— Map of channelName → array of{ time, changePercent, hotspots, hasMotion }motionClusters— sorted array of{ startTime, endTime, channelName, peakChange, ... }isScanning/scanAbort— scan lifecycleisMonitoring— live monitor mode flag
Key functions
detectMotion(videoEl, channelName)— core pixel-diff, returns{ hasMotion, changePercent, hotspots }scanTimeline({ auto })— full scan orchestrator, creates offscreen videos, yields to UI every 12 framesscheduleAutoScan()— debounced auto-trigger after video loadsetScanUIState('scanning'|'idle')— manages rail button grey-out and progress UIseekToMotion(direction)— skip to next/previous motion clustermonitorTick(absTime)— called fromtick()during playback for live detectionbuildMotionClusters()— post-scan cluster identificationrenderMotionResults()— populates panel: sparkline, summary, event cards
UI elements
- Analytics panel — right slide-out (400px), class
.analytics-panel, toggled viarailAnalyticsBtn - Rail button — greyed out with pulsing purple border (
.scanningclass) during auto-scan - Skip-to-motion buttons —
btnPrevMotion/btnNextMotionin transport controls - Keyboard shortcuts — Shift+N (next motion), Shift+P (previous motion)
Two-tier distribution strategy
The browser version provides motion detection only (zero dependencies, fully offline). A future downloadable local app will add:
- Object classification via bundled ONNX/YOLO model (person/vehicle/animal detection)
- BYOA (Bring Your Own Agent) cloud AI integration for scene understanding, license plates, cross-camera tracking
- Optional local LLM support (Ollama) for fully offline AI reasoning
Memory Management
Video elements must be properly destroyed to avoid browser memory exhaustion:
destroyVideoEl(videoEl)— pauses video, removessrc, calls.load()to force browser to release buffered data. Must be called before removing video elements from DOM.video.preload = 'metadata'— all playback videos use metadata-only preloading to avoid buffering entire files into RAM. Scan videos use'auto'temporarily and are destroyed after use.newSession()— comprehensive teardown: stops scan/monitor, destroys all video elements, revokes all blob URLs, nulls blob references, releases WebGL contexts, clears all state.isImportingguard — prevents concurrent file imports which could cause race conditions and duplicate segments.- Slideshow video elements are destroyed on pane transitions and when slideshow is toggled off.
Key Conventions
- All frontend code lives in
templates/index.html— CSS at top, then the IIFE script block - Video elements are created on-demand and hidden (not removed) for performance
- Segment visibility is recalculated every animation frame during playback
- The
batchingSegmentsflag defers rendering during bulk file imports - Keyboard shortcuts are defined inline (Space=play/pause, arrows=seek, S=slideshow, M=magnifier, F=fullscreen, [/]=speed, 0=reset zoom, Shift+N/P=skip motion)
- Motion scan runs automatically on load — the analytics rail button is disabled until scan completes
- Right-side panels (log, analytics) auto-close when the other opens