# MediaLore A self-hosted web UI for browsing media libraries on a NAS or local filesystem. Configure folders as typed libraries — currently supporting **Games** and **Mixed Media** library types. ## Features - **Games library** — displays a grid of game cover art scanned from folders. Each game folder is expected to contain a `.zip` archive and optional artwork (`cover.*`, `widecover.*`). Clicking a game opens a detail modal with a download link for the zip. - **Mixed media library** — a folder-navigable browser that mirrors the directory structure on disk. Videos open in an inline player (with full seek support via HTTP range requests). Images open in a lightbox. Other files are opened in a new tab. - **Library management UI** — add and remove libraries at `/manage` without touching any config files. Configuration persists across restarts in `libraries.json`. - **Path-jailed file serving** — all file access is verified to stay within the configured library root before being served. ## Project Structure ``` MediaLoreWeb/ ├── libraries.json # Runtime library config (managed via UI, do not edit by hand) ├── data/ # Example media (not committed to production) ├── src/ │ ├── app/ │ │ ├── layout.tsx │ │ ├── page.tsx # Home — library cards (redirects to /manage if empty) │ │ ├── manage/page.tsx # Library management — add/remove libraries │ │ ├── library/[id]/page.tsx # Library view (games or mixed) │ │ └── api/ │ │ ├── libraries/route.ts # GET /api/libraries, POST /api/libraries │ │ ├── libraries/[id]/route.ts # DELETE /api/libraries/:id │ │ ├── games/route.ts # GET /api/games?libraryId= │ │ ├── browse/route.ts # GET /api/browse?libraryId=&path= │ │ └── file/route.ts # GET /api/file?libraryId=&path= │ ├── components/ │ │ ├── LibraryCard.tsx │ │ ├── NavLink.tsx │ │ ├── games/ │ │ │ ├── GamesView.tsx │ │ │ └── GameDetailModal.tsx │ │ └── mixed/ │ │ ├── MixedView.tsx │ │ ├── VideoPlayerModal.tsx │ │ └── ImageLightbox.tsx │ ├── lib/ │ │ ├── libraries.ts # Config read/write, path resolution, add/remove helpers │ │ ├── games.ts # Games library scanner │ │ └── files.ts # Mixed library directory scanner │ └── types/ │ └── index.ts ``` ## Developer Setup **Requirements:** Node.js 18+ ```bash # 1. Install dependencies npm install # 2. Start the development server npm run dev ``` Open [http://localhost:3000](http://localhost:3000). Other available commands: ```bash npm run build # Production build npm run start # Start production server (run build first) npm run lint # Run ESLint ``` ## Library Configuration Libraries are managed through the **Manage Libraries** screen at `/manage` in the app. No manual file editing is required. When you add a library via the UI, you provide: | Field | Description | |--------|-------------| | Name | Display name shown in the UI | | Path | Absolute or project-relative path to the library root folder on disk | | Type | `Games` or `Mixed Media` | The app validates that the path exists as a directory before saving. Configuration is stored in `libraries.json` at the project root and persists across restarts. If no libraries are configured, navigating to `/` automatically redirects to `/manage`. Paths can point anywhere on the filesystem — they do not need to be inside the project directory. ## Library Folder Conventions ### Games (`"type": "games"`) Each game is a subdirectory containing: ``` Games/ └── My Game Title/ ├── My Game Title.zip # Required — the downloadable archive ├── cover.png # Optional — portrait cover art (case-insensitive) └── widecover.jpg # Optional — landscape/hero cover art (case-insensitive) ``` - The `.zip` filename can be anything; the first `.zip` found in the folder is used. - Cover art filenames are matched case-insensitively against `cover.*` and `widecover.*`. Any image extension is accepted. ### Mixed Media (`"type": "mixed"`) No specific structure is required. The UI mirrors the directory tree exactly as it exists on disk. Supported media types: - **Video:** `.mp4`, `.mov`, `.mkv`, `.avi`, `.webm`, `.m4v` - **Image:** `.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`, `.bmp`, `.tiff` ## API Reference All API routes are server-side. File paths are never exposed in client-side state — only opaque `/api/file?...` URLs are sent to the browser. | Route | Method | Description | |-------|--------|-------------| | `/api/libraries` | GET | Returns the full configured library list | | `/api/libraries` | POST | Adds a library. Body: `{ name, path, type }`. Validates the path exists. | | `/api/libraries/:id` | DELETE | Removes a library by id | | `/api/games?libraryId=` | GET | Scans the games library and returns structured game entries | | `/api/browse?libraryId=&path=` | GET | Lists the contents of a directory within a mixed library | | `/api/file?libraryId=&path=` | GET | Streams a file; supports HTTP `Range` requests for seekable video playback | ## Tech Stack - [Next.js 15](https://nextjs.org/) (App Router) - [React 19](https://react.dev/) - [TypeScript 5](https://www.typescriptlang.org/) - [Tailwind CSS 4](https://tailwindcss.com/)