Backend (Rust/Actix-web): - Add video_preview_clips table and PreviewDao for tracking preview generation - Add ffmpeg preview clip generator: 10 equally-spaced 1s segments at 480p with CUDA NVENC auto-detection - Add PreviewClipGenerator actor with semaphore-limited concurrent processing - Add GET /video/preview and POST /video/preview/status endpoints - Extend file watcher to detect and queue previews for new videos - Use relative paths consistently for DB storage (matching EXIF convention) Frontend (React Native/Expo): - Add VideoWall grid view with 2-3 column layout of looping preview clips - Add VideoWallItem component with ActiveVideoPlayer sub-component for lifecycle management - Add useVideoWall hook for batch status polling with 5s refresh - Add navigation button in grid header (visible when videos exist) - Use TextureView surface type to fix Android z-ordering issues - Optimize memory: players only mount while visible via FlatList windowSize - Configure ExoPlayer buffer options and caching for short clips - Tap to toggle audio focus, long press to open in full viewer Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
63 lines
2.6 KiB
Markdown
63 lines
2.6 KiB
Markdown
# Data Model: VideoWall
|
|
|
|
## Entities
|
|
|
|
### VideoPreviewClip
|
|
|
|
Tracks the generation status and metadata of preview clips derived from source videos.
|
|
|
|
**Table**: `video_preview_clips`
|
|
|
|
| Field | Type | Constraints | Description |
|
|
|-------|------|-------------|-------------|
|
|
| id | INTEGER | PRIMARY KEY, AUTOINCREMENT | Unique identifier |
|
|
| file_path | TEXT | NOT NULL, UNIQUE | Relative path of the source video from BASE_PATH |
|
|
| status | TEXT | NOT NULL, DEFAULT 'pending' | Generation status: `pending`, `processing`, `complete`, `failed` |
|
|
| duration_seconds | REAL | NULLABLE | Duration of the generated preview clip (≤10s) |
|
|
| file_size_bytes | INTEGER | NULLABLE | Size of the generated MP4 file |
|
|
| error_message | TEXT | NULLABLE | Error details if status is `failed` |
|
|
| created_at | TEXT | NOT NULL | ISO 8601 timestamp when record was created |
|
|
| updated_at | TEXT | NOT NULL | ISO 8601 timestamp when record was last updated |
|
|
|
|
**Indexes**:
|
|
- `idx_preview_clips_file_path` on `file_path` (unique, used for lookups and batch queries)
|
|
- `idx_preview_clips_status` on `status` (used by file watcher to find pending/failed clips)
|
|
|
|
### Relationships
|
|
|
|
- **VideoPreviewClip → Source Video**: One-to-one via `file_path`. The preview clip file on disk is located at `{PREVIEW_CLIPS_DIRECTORY}/{file_path}.mp4`.
|
|
- **VideoPreviewClip → image_exif**: Implicit relationship via shared `file_path`. No foreign key needed — the EXIF table may not have an entry for every video.
|
|
|
|
## State Transitions
|
|
|
|
```
|
|
[new video detected] → pending
|
|
pending → processing (when generation starts)
|
|
processing → complete (when ffmpeg succeeds)
|
|
processing → failed (when ffmpeg fails or times out)
|
|
failed → pending (on retry / re-scan)
|
|
```
|
|
|
|
## Validation Rules
|
|
|
|
- `file_path` must be a valid relative path within BASE_PATH
|
|
- `status` must be one of: `pending`, `processing`, `complete`, `failed`
|
|
- `duration_seconds` must be > 0 and ≤ 10.0 when status is `complete`
|
|
- `file_size_bytes` must be > 0 when status is `complete`
|
|
- `error_message` should only be non-null when status is `failed`
|
|
|
|
## Storage Layout (Filesystem)
|
|
|
|
```
|
|
{PREVIEW_CLIPS_DIRECTORY}/
|
|
├── 2024/
|
|
│ ├── vacation/
|
|
│ │ ├── beach.mp4 # Preview for BASE_PATH/2024/vacation/beach.mov
|
|
│ │ └── sunset.mp4 # Preview for BASE_PATH/2024/vacation/sunset.mp4
|
|
│ └── birthday.mp4 # Preview for BASE_PATH/2024/birthday.avi
|
|
└── 2025/
|
|
└── trip.mp4 # Preview for BASE_PATH/2025/trip.mkv
|
|
```
|
|
|
|
All preview clips use `.mp4` extension regardless of source format.
|