# MediaLore A self-hosted media library browser for images and videos. Organize your local media collections with tagged browsing, a file-system explorer, and a full-screen "doom scroll" viewer — all via a clean, responsive web UI. ## Features - **Media Libraries** — Add local directories as named libraries; files are indexed by relative path within each library - **File Browser** — Navigate your library's directory structure with a thumbnail grid, breadcrumbs, and lazy-loaded previews - **Tagging System** — Organize media with named tags grouped into categories; create tags inline while viewing items - **Search** — Filter by filename (fuzzy match), tag, and/or library - **Doom Scroll** — Full-screen, swipe/scroll-driven media viewer for an immersive, social-media-like experience - **Live File Watching** — Automatically detects new, moved, and deleted files via `watchdog` (no manual rescans needed) - **Auto-Generated Thumbnails** — JPEG thumbnails for images (via Pillow) and videos (via ffmpeg) - **Dark / Light Theme** — Toggle with persistence via `localStorage` - **Responsive** — Mobile-friendly with a collapsible sidebar and touch gestures ## Architecture ``` ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ Frontend │──────▶│ Backend │──────▶│ SQLite DB │ │ React + │ HTTP │ FastAPI + │ ORM │ (SQLite + │ │ TypeScript │ │ Uvicorn │ │ WAL mode) │ │ Vite │ │ │ │ │ └─────────────┘ └──────┬───────┘ └─────────────┘ │ ┌───────▼────────┐ │ File System │ │ (media root) │ └─────────────────┘ ``` **Backend** — Python 3.12+, FastAPI, SQLAlchemy (async), aiosqlite, Alembic for migrations, `watchdog` for file-system events, Pillow for image thumbnails, ffmpeg for video thumbnails. **Frontend** — React 19, TypeScript, Vite 8, React Router 7, TanStack Query, Nginx (static serving + API proxy). ## Quick Start (Docker Compose) ### Prerequisites - Docker & Docker Compose - ffmpeg (bundled in the backend container) - Your media files accessible on the host (e.g. mounted from a NAS) ### 1. Clone & configure ```bash git clone cd MediaLore-Web-App cp .env.example .env ``` Edit `.env` to point `MEDIA_ROOT` to your host media directory: ```env MEDIA_ROOT=/mnt/nas ``` > **Note:** Inside the container, `$MEDIA_ROOT` maps to `/media`. Library paths you add in the UI must be subdirectories of this mount. ### 2. Start the stack ```bash docker compose up --build -d ``` The frontend is available at `http://localhost:8085`. ### 3. Add a library 1. Open **Settings** → **Libraries** 2. Enter a name and a path (e.g. `/media/Images/Photos`) 3. Click **Add Library** — scanning begins in the background The scanner walks the directory tree, computes SHA-256 hashes, detects moved files by hash, and starts a file watcher for live updates. ## Docker Compose Configuration ```yaml services: backend: build: ./backend volumes: - medialore-data:/data # DB + thumbnails - /data/smb/adult/Images:/media/Images - /data/smb/adult/Video Clips:/media/Video Clips environment: - DATABASE_URL=sqlite+aiosqlite:////data/medialore.db - THUMBNAIL_DIR=/data/thumbnails frontend: build: ./frontend ports: - "8085:80" ``` Adjust the volume mounts to match your media layout. The `medialore-data` named volume persists the SQLite database and generated thumbnails across restarts. ## API Endpoints All endpoints are under `/api` and return JSON (except file/thumbnail responses). ### Libraries | Method | Path | Description | |--------|------|-------------| | `GET` | `/libraries` | List all libraries | | `POST` | `/libraries` | Create a library (`{ name, path }`) | | `GET` | `/libraries/:id/scan-status` | Check if a library is currently scanning | | `POST` | `/libraries/:id/rescan` | Trigger a manual rescan | | `GET` | `/libraries/:id/browse` | Browse directory entries (`?path=/sub/dir`) | | `GET` | `/libraries/:id/doom-scroll` | Get all media items in a library (optionally under a path) | | `DELETE` | `/libraries/:id` | Remove a library (stops watcher, deletes records) | ### Media | Method | Path | Description | |--------|------|-------------| | `GET` | `/media/:id` | Get media item details (with tags) | | `GET` | `/media/:id/file` | Stream the original media file | | `GET` | `/media/:id/thumbnail` | Get or generate a thumbnail (JPEG) | | `PUT` | `/media/:id/tags` | Set tags on an item (`{ tag_ids: [1, 2] }`) | ### Tags | Method | Path | Description | |--------|------|-------------| | `GET` | `/tags` | List all tags grouped by category | | `POST` | `/tags` | Create a tag (`{ name, category }`) | | `DELETE` | `/tags/:id` | Delete a tag | ### Search | Method | Path | Description | |--------|------|-------------| | `GET` | `/search?q=foo&tags=1,2&library_id=3` | Search media by filename, tags, and/or library | ## Development ### Backend (local) ```bash cd backend python -m venv .venv source .venv/bin/activate pip install -e ".[dev]" # or: pip install fastapi uvicorn[standard] sqlalchemy aiosqlite alembic pydantic-settings watchdog Pillow python-multipart uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 ``` ### Frontend (local) ```bash cd frontend npm install npm run dev ``` The Vite dev server runs at `http://localhost:5173`. Configure your proxy or set `VITE_API_BASE` to point to the backend. ## Data Model ``` ┌──────────┐ 1..* ┌────────────┐ *..* ┌─────┐ │ Library │─────────────────▶│ MediaItem │─────────────────▶│ Tag │ └──────────┘ └────────────┘ └─────┘ • id • id • name • library_id • path • rel_path • filename • file_hash (SHA-256) • media_type (image | video) • size_bytes • missing (file deleted from disk) • created_at • updated_at • tags[] ``` ## Tech Stack | Layer | Technology | |-------|-----------| | Frontend | React 19, TypeScript, Vite, React Router, TanStack Query | | Backend | Python 3.12+, FastAPI, Uvicorn | | Database | SQLite (async via aiosqlite, WAL mode) | | Migrations | Alembic | | Thumbnails | Pillow (images), ffmpeg (videos) | | File Watching | watchdog | | Containerization | Docker (Python 3.12-slim, Node 20-alpine, Nginx Alpine) | ## License [Add your license here]