vigilar/docs/superpowers/specs/2026-04-03-daily-delight-design.md
Aaron D. Lee 93492e33d8 Add daily delight design spec (Q1, Q4, Q6)
Highlight reel video, kiosk ambient mode for magic picture frame,
and on-demand time-lapse generator with scheduling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 15:20:20 -04:00

12 KiB

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):

[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

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/<id>/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 <img> 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

[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/<id>/timelapse

Request body:

{
    "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/<id>/timelapse/status returns progress of any in-flight generation:

{"status": "generating", "progress": 0.65, "eta_seconds": 12}

Or {"status": "idle"} when nothing is running.

Scheduled Presets

Route: POST /cameras/<id>/timelapse/schedule

Request body:

{
    "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/<id>/timelapse/schedules — list schedules for a camera
  • DELETE /cameras/<id>/timelapse/schedule/<schedule_id> — 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)