This repository has been archived on 2026-06-15. You can view files and clone it, but cannot push or open issues or pull requests.
Garret Patti 122d7aa332 add series grouping, cover upload, and multi-zip download to games library
- Series grouping: a top-level folder with no .zip but game subfolders is
  now treated as a GameSeries. Clicking a series drills into it with a
  breadcrumb; a game-count badge distinguishes series cards from game cards.
  Series fall back to the first game's cover when no series-level cover exists.
- Cover upload: new POST /api/game-cover endpoint writes cover.jpg or
  widecover.jpg directly into the game/series folder (re-encoded via sharp).
  A kebab menu on GameDetailModal opens an Edit Images panel showing previews
  and upload/replace buttons for both cover and wide cover.
- Multi-zip download: Game.zipFiles replaces zipPath and includes all .zip
  files in the folder. A single zip shows the existing download button; multiple
  zips render a split button — primary action downloads the first file, a
  dropdown arrow lists all files by name.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 12:49:42 -04:00
2026-04-05 10:13:42 -04:00
2026-03-26 23:41:52 -04:00
2026-03-25 19:51:57 -04:00
2026-03-26 23:41:52 -04:00
2026-04-05 10:01:34 -04:00
2026-03-25 16:18:23 -04:00
2026-03-25 16:18:23 -04:00
2026-03-26 23:41:52 -04:00
2026-03-25 16:18:23 -04:00
2026-03-25 16:59:09 -04:00
2026-03-25 16:18:23 -04:00

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. Image and video files display auto-generated square thumbnails. 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.
  • Thumbnail generation — lazy on-demand thumbnails for images (via sharp) and video frames (via ffmpeg). Thumbnails are cached to disk in .thumbnails/ and regenerated automatically when the source file changes.
  • 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)
├── .thumbnails/            # Disk cache for generated thumbnails (auto-created, gitignored)
├── 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=
│   │       └── thumbnail/route.ts      # GET /api/thumbnail?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
│   │   └── thumbnails.ts   # Thumbnail cache + generation (sharp / ffmpeg)
│   └── types/
│       └── index.ts

Developer Setup

Requirements: Node.js 18+, ffmpeg and ffprobe (for video thumbnails)

Video thumbnails require ffmpeg and ffprobe to be installed and available on $PATH. If they are missing, video tiles gracefully fall back to a generic icon — no errors are thrown.

macOS: brew install ffmpeg
Ubuntu/Debian: sudo apt install ffmpeg
Windows: Download from ffmpeg.org and add to PATH

# 1. Install dependencies
npm install

# 2. Start the development server
npm run dev

Open http://localhost:3000.

Other available commands:

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=<id> GET Scans the games library and returns structured game entries
/api/browse?libraryId=<id>&path=<subpath> GET Lists the contents of a directory within a mixed library
/api/file?libraryId=<id>&path=<relpath> GET Streams a file; supports HTTP Range requests for seekable video playback
/api/thumbnail?libraryId=<id>&path=<relpath> GET Returns a cached square thumbnail (JPEG) for an image or video file; 404 if generation fails or ffmpeg is unavailable

Tech Stack

Description
A web app for accessing media
Readme 2.5 MiB
Languages
TypeScript 99.7%
Dockerfile 0.2%