# Daily Delight — Design Spec ## Overview Three features that make Vigilar enjoyable to look at every day: an auto-generated daily highlight reel video, a kiosk ambient mode for a magic picture frame, and an on-demand time-lapse generator with scheduling. ## Q1: Daily Highlight Reel ### What It Does Each morning, auto-generates a 60-90 second MP4 montage of yesterday's most interesting events — pet zoomies, wildlife visits, deliveries, notable detections. Generated once, viewable on the dashboard, kiosk, and phone via push notification. ### Event Scoring Events are ranked by a scoring heuristic to select the top N clips (default 10, configurable): | Event Type | Score | |-----------|-------| | PET_ESCAPE | 10 | | WILDLIFE_PREDATOR | 9 | | PACKAGE_DELIVERED | 8 | | UNKNOWN_ANIMAL | 7 | | PET_DETECTED (high motion) | 6 | | WILDLIFE_NUISANCE | 5 | | PERSON_DETECTED | 4 | | UNKNOWN_VEHICLE_DETECTED | 4 | | WILDLIFE_PASSIVE | 3 | | VEHICLE_DETECTED | 2 | | PET_DETECTED (normal) | 1 | "High motion" for pet detections means the motion confidence was above the `zoomie_threshold` (0.8) from pet activity config. ### FFmpeg Pipeline 1. For each selected event, extract a 5-second clip centered on the event timestamp from the associated recording segment (decrypt if needed). 2. Overlay a date/time watermark and event label (e.g., "Angel — Kitchen — 2:34 PM") on each clip using FFmpeg's `drawtext` filter. 3. Concatenate clips with 0.5-second crossfade transitions using the `xfade` filter. 4. Output as H.264 MP4, CRF 23, resolution matching the source (or downscaled to 1280x720 if sources vary). ### New File: `vigilar/highlights/reel.py` `generate_daily_reel(engine, recordings_dir, date, config) -> str`: - Queries events for the given date, scores and ranks them - Extracts clips from recordings via FFmpeg subprocess - Concatenates with transitions and watermarks - Saves to `recordings_dir/highlights/YYYY-MM-DD.mp4` - Inserts a recording row with `trigger=HIGHLIGHT` - Returns the file path `ReelConfig` (from config): ```toml [highlights] enabled = true generate_time = "06:00" # when to generate yesterday's reel max_clips = 10 # max events to include clip_duration_s = 5 # seconds per clip cameras = [] # empty = all cameras event_types = [] # empty = all types ``` ### Config Model ```python class HighlightsConfig(BaseModel): enabled: bool = True generate_time: str = "06:00" max_clips: int = 10 clip_duration_s: int = 5 cameras: list[str] = Field(default_factory=list) event_types: list[str] = Field(default_factory=list) ``` Add to `VigilarConfig` as `highlights: HighlightsConfig`. ### Scheduling The daily reel generation runs as a scheduled job in the health monitor process (which already handles the daily digest timing). At `generate_time`, it calls `generate_daily_reel()` for yesterday's date. ### Delivery - **Dashboard:** "Yesterday's Highlights" card on the main dashboard. Shows thumbnail + duration + "Play" button. Links to `/recordings//download`. - **Push notification:** After generation, sends a Web Push notification: "Your daily highlight reel is ready (8 clips, 52s)". Link opens the recording. - **Kiosk:** Auto-plays at a configurable time (see Q4 kiosk spec). Then returns to ambient mode. - **Dashboard config:** Settings page section for highlights — toggle cameras, event types, generation time, max clips. ### Recording Integration Highlight reels are stored as regular recordings with `trigger=HIGHLIGHT`. They appear in the recordings list with a distinct badge. The `RecordingTrigger` enum already has values for different trigger types — add `HIGHLIGHT` and `TIMELAPSE`. ## Q4: Kiosk Ambient Mode ### What It Does Extends the existing kiosk blueprint with an ambient "all-quiet" state optimized for a magic picture frame (RPi + thin LCD). When nothing is happening, it shows a beautiful home awareness dashboard. When alerts fire, the relevant camera takes over the screen. ### Ambient Screen Layout Fullscreen, no nav, no scrollbar. Landscape orientation. Dark theme with muted colors to avoid light pollution. **Top bar (10% height):** - Current time (large, `hh:mm`, updates every minute) - Current date (weekday + month + day) - Weather icon + temperature (from Open-Meteo, cached hourly, "—" if offline) **Center area (70% height):** - Rotating camera thumbnail: cycles through cameras every 10 seconds - Shows the camera name and a subtle "live" indicator - Thumbnail source: latest HLS segment frame or snapshot endpoint - Smooth CSS crossfade transition between cameras **Bottom bar (20% height):** - Household presence: who's home (icons + names from presence monitor) - Pet locations: pet name + last-seen camera + time ago (e.g., "Angel — Kitchen — 4m ago") - If highlight reel is available: small "Play highlights" button ### Alert Takeover When an alert event arrives via SSE (`/events/stream`): 1. Fade ambient layout to 0 opacity (300ms) 2. Show the relevant camera's live HLS feed fullscreen 3. Overlay event info: type badge, camera name, timestamp 4. After timeout (configurable, default 30s for normal, 60s for predator alerts), if no new events, fade back to ambient 5. If a new event fires during takeover, reset the timer and switch cameras if different ### Highlight Reel Playback If a daily highlight reel exists for yesterday, auto-play it once at a configurable time. After playback, return to ambient. Configurable in the kiosk settings. ### Power / Performance (RPi Considerations) - No heavy canvas rendering — all layout via CSS Grid/Flexbox - Camera thumbnails are static `` elements swapped via JS, not video streams - CSS `will-change: opacity` on transition elements - Minimal DOM manipulation — update text content only when values change - Screen dimming: configurable dark hours (e.g., 23:00-06:00) via a CSS overlay with `opacity: 0.1` that dims the screen. Optionally pair with HDMI CEC `tvservice -o` for full screen-off (documented as optional). - Auto-refresh: full page reload every 6 hours to prevent memory leaks in long-running browser tabs ### Config ```toml [kiosk] ambient_enabled = true camera_rotation_s = 10 # seconds between camera switches alert_timeout_s = 30 # seconds before returning to ambient predator_alert_timeout_s = 60 # predator alerts stay longer dim_start = "23:00" # screen dim start dim_end = "06:00" # screen dim end highlight_play_time = "07:00" # when to auto-play highlight reel ``` ### Files **New file:** `vigilar/web/templates/kiosk/ambient.html` — self-contained fullscreen template **Modified:** `vigilar/web/blueprints/kiosk.py` — add `GET /kiosk/ambient` route **Modified:** `vigilar/config.py` — add `KioskConfig` model ### Weather Dependency Uses `vigilar/detection/weather.py` (WeatherFetcher) from Group C's wildlife spec. If Group C isn't built yet when this is implemented, the weather section simply shows "—" (graceful degradation, no hard dependency). ## Q6: Time-lapse Generator ### What It Does Generate time-lapse videos on demand for any camera and date range, with optional scheduled presets for automatic daily generation. ### On-Demand Generation **Route:** `POST /cameras//timelapse` Request body: ```json { "date": "2026-04-02", "start_hour": 6, "end_hour": 20, "fps": 30 } ``` Process: 1. Find all recording segments for the camera on the given date within the hour range 2. Extract one frame per minute from each segment (decrypt if needed) via FFmpeg 3. Stitch frames into a time-lapse MP4 at the requested FPS (default 30 — one minute of real time per frame at 30fps gives a ~14-hour day in ~28 seconds) 4. Save as a recording with `trigger=TIMELAPSE` 5. Return `{id, status: "generating"}` immediately — generation runs in a background thread **Status polling:** `GET /cameras//timelapse/status` returns progress of any in-flight generation: ```json {"status": "generating", "progress": 0.65, "eta_seconds": 12} ``` Or `{"status": "idle"}` when nothing is running. ### Scheduled Presets **Route:** `POST /cameras//timelapse/schedule` Request body: ```json { "name": "Daily backyard", "start_hour": 6, "end_hour": 20, "time": "20:00" } ``` Creates a daily schedule: every day at the specified time, auto-generate a timelapse for that camera covering the hour range. **Management routes:** - `GET /cameras//timelapse/schedules` — list schedules for a camera - `DELETE /cameras//timelapse/schedule/` — remove a schedule ### Database **New table: `timelapse_schedules`** | Column | Type | Notes | |--------|------|-------| | id | Integer PK | Autoincrement | | camera_id | String NOT NULL | Which camera | | name | String NOT NULL | User-given label | | start_hour | Integer NOT NULL | Start hour (0-23) | | end_hour | Integer NOT NULL | End hour (0-23) | | generate_time | String NOT NULL | "HH:MM" daily generation time | | enabled | Integer NOT NULL | 1 = active | | created_at | Float NOT NULL | Timestamp | ### New File: `vigilar/highlights/timelapse.py` `generate_timelapse(camera_id, date, start_hour, end_hour, fps, recordings_dir, engine) -> str`: - Queries recordings for the camera + date + hour range - Extracts frames via FFmpeg (1 per minute) - Stitches into MP4 - Inserts recording row with `trigger=TIMELAPSE` - Returns file path `check_schedules(engine, recordings_dir)`: - Called periodically by the health monitor (every 60 seconds) - Finds schedules where `enabled=1` and current time matches `generate_time` (within 60-second window) - Triggers `generate_timelapse()` for today's date - Tracks "last generated" to avoid double-generation ### UI **Camera page:** "Time-lapse" tab alongside the existing stream and heatmap views. - Date picker + start/end hour sliders + "Generate" button - Progress bar when generating - List of generated time-lapses (from recordings with `trigger=TIMELAPSE`) - "Schedule daily" toggle that creates/removes a preset ### New Event Types Add to `RecordingTrigger` enum: - `HIGHLIGHT` — daily highlight reel - `TIMELAPSE` — time-lapse video (Note: `HIGHLIGHT` and `TIMELAPSE` are recording triggers, not event types — they go in `RecordingTrigger`, not `EventType`.) ## Dependencies ### New Packages None — FFmpeg is already used by the recorder. All generation is via subprocess calls. ### New Files | File | Purpose | |------|---------| | `vigilar/highlights/reel.py` | Daily highlight reel generator | | `vigilar/highlights/timelapse.py` | Time-lapse generator + schedule checker | | `vigilar/web/templates/kiosk/ambient.html` | Ambient mode fullscreen template | ### Modified Files | File | Changes | |------|---------| | `vigilar/config.py` | Add HighlightsConfig, KioskConfig models | | `vigilar/constants.py` | Add HIGHLIGHT, TIMELAPSE to RecordingTrigger | | `vigilar/storage/schema.py` | Add timelapse_schedules table | | `vigilar/storage/queries.py` | Add timelapse schedule CRUD functions | | `vigilar/web/blueprints/kiosk.py` | Add /kiosk/ambient route | | `vigilar/web/blueprints/cameras.py` | Add timelapse generation + schedule routes | | `vigilar/health/monitor.py` | Add highlight reel + timelapse schedule triggers | | `vigilar/web/app.py` | No new blueprints (uses existing kiosk + cameras) | ## Out of Scope - Highlight reel with background music (YAGNI) - Time-lapse with motion interpolation / smooth zoom (just frame concat) - Kiosk touch controls (RPi magic frame is view-only) - Multiple highlight reel presets (one per day is sufficient) - Streaming time-lapse generation (generate full file, then serve)