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>
This commit is contained in:
parent
1338f7213d
commit
93492e33d8
293
docs/superpowers/specs/2026-04-03-daily-delight-design.md
Normal file
293
docs/superpowers/specs/2026-04-03-daily-delight-design.md
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
# 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/<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
|
||||||
|
|
||||||
|
```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/<id>/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/<id>/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/<id>/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/<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)
|
||||||
Loading…
Reference in New Issue
Block a user