Media Management
Open Genie indexes, catalogs, and serves media files (video, audio, images, documents) from the local file system. It supports automatic metadata extraction, thumbnail generation, playlists, and playback position tracking.
Architecture
File System Changes ──→ Chokidar Watcher ──→ Indexer ──→ Database
↓
Metadata Extraction
↓
Thumbnail Generation
File Watcher (lib/media/watcher.ts)
Uses chokidar to monitor the storage directory for file changes.
Behavior
- Watched events:
add,change,unlink - Debounce: 2 seconds — rapid changes are batched
- Ignored: dotfiles,
node_modules,.thumbnails,.cache - On add/change: Re-index the file (extract metadata, generate thumbnail)
- On unlink: Remove the file record from the database
Start/Stop
import { startMediaWatcher, stopMediaWatcher } from "@/lib/media/watcher";
startMediaWatcher(); // Called at boot
stopMediaWatcher(); // Called at shutdown
Indexer (lib/media/indexer.ts)
Scans storage directories and syncs the database with the file system.
fullScan()
Performs a complete scan at boot:
- Walk all files in the storage directory
- For each file, check if it exists in the database
- New files: Extract metadata, generate thumbnail, insert record
- Changed files: Re-extract metadata, update record
- Missing files: Remove database records for deleted files
Returns a summary:
const result = await fullScan();
// { added: 12, updated: 3, removed: 1, errors: [] }
Media Type Detection
Files are classified by MIME type:
| Type | Extensions |
|---|---|
video | .mp4, .mkv, .avi, .mov, .webm, etc. |
audio | .mp3, .flac, .m4a, .wav, .ogg, etc. |
image | .jpg, .png, .gif, .webp, .svg, etc. |
document | .pdf, .txt, .doc, .docx, etc. |
Metadata Extraction (lib/media/metadata.ts)
Different metadata is extracted based on file type:
Video
- Duration, resolution (width/height)
- Codec, bitrate
- Container format
Audio
- Title, artist, album, year, genre
- Track number, disc number
- Duration, bitrate, sample rate
Image
- Dimensions (width/height)
- EXIF data: date taken, GPS coordinates, camera model
- Color space, orientation
Document
- File size and MIME type only
Thumbnail Generation (lib/media/thumbnails.ts)
Thumbnails are generated using Sharp and FFmpeg.
| Source Type | Method |
|---|---|
| Video | Extract frame at 10% of duration via FFmpeg |
| Audio | Extract embedded album art (if available) |
| Image | Resize to 300px width, JPEG quality 80 |
| Document | No thumbnail |
Thumbnails are stored in data/files/.thumbnails/{fileId}.jpg.
Playlists (lib/media/playlists.ts)
Playlists organize media files into ordered collections.
Operations
// Create a playlist
createPlaylist(name: string, description?: string): Promise<Playlist>
// Add a media file to a playlist
addToPlaylist(playlistId: string, mediaFileId: string, position?: number): Promise<void>
// Remove from playlist
removeFromPlaylist(playlistId: string, itemId: string): Promise<void>
// Reorder items (auto-shifts positions)
reorderPlaylistItem(playlistId: string, itemId: string, newPosition: number): Promise<void>
// Get playlist with items
getPlaylistWithItems(playlistId: string): Promise<PlaylistWithItems>
Position Management
Items have a position integer for ordering. When inserting at a specific position, existing items shift up automatically.
Playback Position Tracking
The lastPosition field on mediaFiles tracks where the user left off in video/audio playback:
POST /api/media/{id}/position
Body: { "position": 1234.5 }
This enables resume-from-where-you-left-off across devices.
REST API
| Method | Path | Description |
|---|---|---|
GET | /api/media | List media (paginated, filterable, sortable) |
POST | /api/media/upload | Upload a file |
GET | /api/media/scan | Trigger a full rescan |
GET | /api/media/[id] | Get file details + metadata |
GET | /api/media/[id]/thumbnail | Serve thumbnail image |
POST | /api/media/[id]/position | Update playback position |
GET | /api/media/photos | List image files |
GET | /api/media/library | Library statistics |
GET | /api/playlists | List playlists |
POST | /api/playlists | Create playlist |
GET | /api/playlists/[id] | Get playlist with items |
PUT | /api/playlists/[id] | Update playlist |
DELETE | /api/playlists/[id] | Delete playlist |
POST | /api/playlists/[id]/items | Add item to playlist |
DELETE | /api/playlists/[id]/items/[itemId] | Remove item |
Query Parameters for GET /api/media
| Parameter | Type | Description |
|---|---|---|
type | string | Filter by media type (video, audio, image, document) |
search | string | Search by filename |
sort | string | Sort field (name, size, createdAt, type) |
order | string | asc or desc |
page | number | Page number (1-based) |
limit | number | Items per page (default: 50) |
Key Files
| File | Purpose |
|---|---|
lib/media/watcher.ts | File system change monitoring |
lib/media/indexer.ts | Full directory scan and sync |
lib/media/metadata.ts | Metadata extraction (EXIF, music tags, video info) |
lib/media/thumbnails.ts | Thumbnail generation |
lib/media/playlists.ts | Playlist CRUD and ordering |