Initial commit — Alta Video Player (WebAVP)

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>
This commit is contained in:
2026-04-04 12:50:24 +00:00
commit 691f643edc
6 changed files with 5568 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
# 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
```bash
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 /` — serves `templates/index.html`
- `GET /static/*` — serves static files with path traversal protection
- `GET /api/verify-cert?serial=...&certificateHash=...` — proxies certificate verification to `https://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 (`isImporting` flag)
- **Multi-camera grid** — responsive CSS grid (19 cameras), drag-to-reorder, click-to-expand
- **Playback engine** — `requestAnimationFrame` tick loop, per-channel segment visibility management, variable speed (0.25x8x), 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
1. **Auto-scan on load**`scheduleAutoScan()` is called from `flushPending()` and `loadSessionData()` after videos finish loading. Debounced 800ms to batch all segments.
2. **Dedicated scan videos** — scan creates temporary offscreen `<video>` elements per segment (with `preload='auto'` for fast seeking) that don't interfere with the playback engine. Each is destroyed after its segment is processed.
3. **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.
4. **Sensitivity slider (1100)** — maps to two internal thresholds via `sensitivityToThresholds()`: `pixelThreshold` (how different a pixel must be) and `changeThreshold` (what % of pixels must change). Presets: Indoor (30), Default (40), Parking (45), Outdoor (55).
5. **Motion clusters** — consecutive motion detections within `clusterGap` (5s) are merged into clusters with start/end times, peak change %, and hotspot data.
6. **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` (1100), `scanInterval` (2s default), `clusterGap` (5s)
- `detector.canvas` / `detector.ctx` — 160x120 offscreen canvas for pixel comparison
- `detector.prevFrames` — Map of channelName → previous ImageData
- `motionProfiles` — Map of channelName → array of `{ time, changePercent, hotspots, hasMotion }`
- `motionClusters` — sorted array of `{ startTime, endTime, channelName, peakChange, ... }`
- `isScanning` / `scanAbort` — scan lifecycle
- `isMonitoring` — 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 frames
- `scheduleAutoScan()` — debounced auto-trigger after video load
- `setScanUIState('scanning'|'idle')` — manages rail button grey-out and progress UI
- `seekToMotion(direction)` — skip to next/previous motion cluster
- `monitorTick(absTime)` — called from `tick()` during playback for live detection
- `buildMotionClusters()` — post-scan cluster identification
- `renderMotionResults()` — populates panel: sparkline, summary, event cards
### UI elements
- **Analytics panel** — right slide-out (400px), class `.analytics-panel`, toggled via `railAnalyticsBtn`
- **Rail button** — greyed out with pulsing purple border (`.scanning` class) during auto-scan
- **Skip-to-motion buttons** — `btnPrevMotion` / `btnNextMotion` in 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, removes `src`, 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.
- **`isImporting` guard** — 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 `batchingSegments` flag 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