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 819748d1ff DB-first library reads, mixed library indexing, and manual NFO refresh
- API reads now serve from media_items cache instead of scanning the filesystem
  on every request; scans (manual or scheduled) remain the write path
- NFO metadata is no longer parsed automatically during scans; title falls back
  to folder/filename — metadata can be refreshed per-item via the kabob menu
- Mixed libraries are now indexed in media_items (new mixed_file item type)
  with file_path stored; scanMixed walks recursively and upserts all files
- Added file_path column to media_items and migrated item_type CHECK constraint
  to include mixed_file via safe table-recreation migration
- New POST /api/nfo-refresh endpoint reads the .nfo for a single item and
  patches its DB row (supports movie, tv_series, tv_episode)
- Added "Refresh metadata" button to movie and TV series kabob menus

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 18:20:21 -04:00
2026-04-05 10:13:42 -04:00
2026-03-26 23:41:52 -04:00
2026-04-05 19:59:24 -04:00
2026-04-05 17:44:24 -04:00
2026-04-05 19:59:24 -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-04-05 18:55:53 -04:00
2026-04-05 18:55:53 -04:00
2026-03-25 16:18:23 -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 — supporting Games, Movies, TV, and Mixed Media library types.

Features

  • Games library — grid of game cover art scanned from folders. Each game folder contains a .zip archive and optional artwork (cover.*, widecover.*). Clicking a game opens a detail modal with a download link.
  • Movies library — grid of movie posters scanned from per-movie folders. Reads .nfo sidecar files (Kodi-compatible) for metadata (title, year, plot, rating, genres). Clicking a movie opens a full-screen video player.
  • TV library — browse TV series → seasons → episodes. Reads tvshow.nfo and per-episode .nfo files for metadata. Supports standard season folder layouts and flat (seasonless) series.
  • Mixed media library — folder-navigable browser that mirrors the directory structure on disk. Images open in a lightbox; videos open in an inline player with full seek support.
  • Thumbnail generation — lazy on-demand thumbnails for images (via sharp) and video frames (via ffmpeg). Thumbnails are cached to disk in .thumbnails/ and regenerated when the source file changes.
  • Tag system — create tag categories and items, then assign tags to individual media items per-library. Filter any library view by one or more tags.
  • Library management UI — add and remove libraries at /manage without touching config files. Configuration persists across restarts in libraries.json.
  • User authentication — iron-session cookie auth with admin and user roles. Admins manage libraries, scan settings, tags, and users. Users have read-only access, optionally restricted to specific libraries.
  • Background scanner — scans all libraries on demand (or on a schedule) to pre-generate thumbnails and populate metadata caches.
  • 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)
├── src/
│   ├── app/
│   │   ├── layout.tsx
│   │   ├── page.tsx                         # Home — library cards (redirects to /manage if empty)
│   │   ├── manage/page.tsx                  # Library management
│   │   ├── manage/tags/page.tsx             # Tag management
│   │   ├── manage/users/page.tsx            # User management (admin only)
│   │   ├── manage/scan/page.tsx             # Scan settings and manual trigger
│   │   ├── library/[id]/page.tsx            # Library view (games / movies / tv / mixed)
│   │   └── api/
│   │       ├── auth/login/route.ts          # POST /api/auth/login
│   │       ├── auth/logout/route.ts         # POST /api/auth/logout
│   │       ├── auth/register/route.ts       # POST /api/auth/register
│   │       ├── libraries/route.ts           # GET, POST /api/libraries
│   │       ├── libraries/[id]/route.ts      # PATCH, DELETE /api/libraries/:id
│   │       ├── games/route.ts               # GET /api/games
│   │       ├── movies/route.ts              # GET /api/movies
│   │       ├── tv/route.ts                  # GET /api/tv (series, seasons, episodes)
│   │       ├── browse/route.ts              # GET /api/browse (mixed media)
│   │       ├── file/route.ts                # GET /api/file
│   │       ├── thumbnail/route.ts           # GET /api/thumbnail
│   │       ├── game-cover/route.ts          # POST /api/game-cover (upload cover art)
│   │       ├── library-cover/[id]/route.ts  # GET /api/library-cover/:id
│   │       ├── scan/route.ts                # POST /api/scan
│   │       ├── scan-settings/route.ts       # GET, POST /api/scan-settings
│   │       ├── settings/route.ts            # GET, POST /api/settings
│   │       ├── tags/categories/route.ts     # GET, POST /api/tags/categories
│   │       ├── tags/categories/[id]/route.ts# PATCH, DELETE /api/tags/categories/:id
│   │       ├── tags/items/route.ts          # GET, POST /api/tags/items
│   │       ├── tags/items/[id]/route.ts     # PATCH, DELETE /api/tags/items/:id
│   │       ├── tags/assignments/route.ts    # GET, POST, DELETE /api/tags/assignments
│   │       ├── tags/library-assignments/route.ts # GET /api/tags/library-assignments
│   │       ├── users/route.ts               # GET, POST /api/users
│   │       ├── users/[id]/route.ts          # PATCH, DELETE /api/users/:id
│   │       └── users/[id]/permissions/route.ts   # GET, POST /api/users/:id/permissions
│   ├── components/
│   │   ├── FilterPanel.tsx
│   │   ├── LibraryCard.tsx
│   │   ├── NavLink.tsx
│   │   ├── DoomScrollView.tsx
│   │   ├── games/
│   │   │   ├── GamesView.tsx
│   │   │   └── GameDetailModal.tsx
│   │   ├── movies/
│   │   │   ├── MoviesView.tsx
│   │   │   └── MovieDetailModal.tsx
│   │   ├── tv/
│   │   │   └── TvView.tsx
│   │   ├── mixed/
│   │   │   ├── MixedView.tsx
│   │   │   ├── VideoPlayerModal.tsx
│   │   │   └── ImageLightbox.tsx
│   │   └── tags/
│   │       └── TagSelector.tsx
│   ├── lib/
│   │   ├── auth.ts          # iron-session auth, requireAdmin / requireLibraryAccess helpers
│   │   ├── db.ts            # SQLite setup and migrations (better-sqlite3)
│   │   ├── libraries.ts     # Config read/write, path resolution, path-jailing
│   │   ├── media-utils.ts   # Shared constants (HIDDEN_FILES, VIDEO_EXTENSIONS) and helpers
│   │   ├── games.ts         # Games library scanner
│   │   ├── movies.ts        # Movies library scanner
│   │   ├── tv.ts            # TV library scanner (series / seasons / episodes)
│   │   ├── files.ts         # Mixed media directory scanner
│   │   ├── nfo.ts           # Kodi-compatible .nfo XML parser
│   │   ├── scanner.ts       # Full-library background scan orchestrator
│   │   ├── thumbnails.ts    # Thumbnail cache + generation (sharp / ffmpeg)
│   │   ├── tags.ts          # Tag CRUD and assignment helpers
│   │   ├── users.ts         # User CRUD and permission helpers
│   │   └── app-settings.ts  # App-level settings (scan schedule, etc.)
│   ├── hooks/
│   │   └── useUserSettings.ts
│   └── 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. Copy the example env file and fill in SESSION_SECRET
cp .env.example .env.local

# 3. Start the development server
npm run dev

Open http://localhost:3000. On first run you will be prompted to create an admin account.

Other available commands:

npm run build   # Production build
npm run start   # Start production server (run build first)
npm run lint    # Run ESLint

Environment Variables

Variable Required Description
SESSION_SECRET Yes Secret used to sign session cookies. Must be at least 32 characters.
COOKIE_SECURE No Set to true in production (HTTPS). Defaults to false.

Authentication

MediaLore uses cookie-based sessions (iron-session). On first launch, navigate to /register to create the initial admin account. Subsequent registrations require an existing admin to create accounts via /manage/users.

Roles:

Role Capabilities
admin Full access: manage libraries, users, tags, scan settings
user Read-only access to assigned libraries

Library-level permissions can be configured per user at /manage/users.

Library Configuration

Libraries are managed through the Manage Libraries screen at /manage in the app.

Field Description
Name Display name shown in the UI
Path Absolute or project-relative path to the library root folder on disk
Type games, movies, tv, or mixed

The app validates that the path exists as a directory before saving. Configuration is stored in libraries.json at the project root.

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)

Subdirectories without a .zip are treated as series containers — their child directories are scanned as individual games.

Movies ("type": "movies")

Each movie is a subdirectory containing a single video file and optional sidecar files:

Movies/
└── The Matrix (1999)/
    ├── The Matrix (1999).mkv   # Required — any supported video extension
    ├── movie.nfo               # Optional — Kodi-compatible metadata
    ├── poster.jpg              # Optional — portrait poster art
    └── backdrop.jpg            # Optional — backdrop/fanart image

Supported video extensions: .mkv, .mp4, .avi, .mov, .m4v, .wmv, .ts, .m2ts

Poster filenames are matched case-insensitively against poster, cover, or folder. Backdrop filenames are matched against backdrop, fanart, or background.

TV ("type": "tv")

TV/
└── Breaking Bad/
    ├── tvshow.nfo              # Optional — series metadata
    ├── poster.jpg              # Optional — series poster
    ├── Season 01/
    │   ├── s01e01.mkv
    │   ├── s01e01.nfo          # Optional — episode metadata
    │   └── ...
    └── Season 02/
        └── ...

Season directory names are matched against patterns: Season 01, S01, 1, 01. If no season subdirectories contain video files, the series root itself is treated as a flat (seasonless) season.

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.

Auth

Route Method Description
/api/auth/login POST Authenticate. Body: { username, password }
/api/auth/logout POST Clear session cookie
/api/auth/register POST Create account. Body: { username, password }. First user becomes admin.

Libraries

Route Method Description
/api/libraries GET Returns the full configured library list
/api/libraries POST Adds a library. Body: { name, path, type }
/api/libraries/:id PATCH Updates a library
/api/libraries/:id DELETE Removes a library by id

Media

Route Method Description
/api/games?libraryId= GET Scans the games library and returns structured game entries
/api/movies?libraryId= GET Scans the movies library and returns movie entries
/api/tv?libraryId= GET Returns TV series list
/api/tv?libraryId=&seriesId= GET Returns seasons for a series
/api/tv?libraryId=&seriesId=&seasonId= GET Returns episodes for a season
/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
/api/thumbnail?libraryId=&path= GET Returns a cached square thumbnail (JPEG); 404 if generation fails

Tags

Route Method Description
/api/tags/categories GET, POST List or create tag categories
/api/tags/categories/:id PATCH, DELETE Update or delete a category
/api/tags/items GET, POST List or create tag items
/api/tags/items/:id PATCH, DELETE Update or delete a tag item
/api/tags/assignments GET, POST, DELETE Get, add, or remove tag assignments on a media item
/api/tags/library-assignments?libraryId= GET All tag assignments for a library (used by filter panel)

Users & Settings

Route Method Description
/api/users GET, POST List or create users (admin only)
/api/users/:id PATCH, DELETE Update or delete a user
/api/users/:id/permissions GET, POST Get or set library-level permissions for a user
/api/settings GET, POST App-level settings
/api/scan POST Trigger a full library scan
/api/scan-settings GET, POST Get or update scan schedule settings

Tech Stack

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