# Mulita - Self-Hosted Photo Management Application A self-hosted, Docker-deployed photo management application inspired by Lightroom's workflow. Mulita provides a fast, keyboard-driven interface to browse, organize, tag, and manage your photo library. ## Features - **Photo Organization**: Browse photos in a timeline view with virtual scrolling for performance - **Thumbnail Generation**: Automatic thumbnail generation for all photo formats including RAW - **Metadata Extraction**: Full EXIF/XMP metadata extraction and search - **Keyboard Shortcuts**: Lightroom-style keyboard navigation and actions - **File Support**: JPEG, PNG, RAW formats (CR2, CR3, NEF, ARW, etc.), HEIC/HEIF, and videos - **Heaps**: Temporary collections for organizing photos - **Tags & Ratings**: Organize with tags, star ratings, and color labels - **Dark Mode**: Photography-optimized dark interface ## Tech Stack ### Backend - Python 3.12 with FastAPI - SQLite with SQLAlchemy (async) - Celery + Redis for background tasks - pyvips for fast thumbnail generation - ExifTool for metadata extraction ### Frontend - React 18 with TypeScript - Vite for fast development - TanStack Query for data fetching - TanStack Virtual for virtualized scrolling - Tailwind CSS for styling - Zustand for state management ## Quick Start ### Prerequisites - Docker and Docker Compose ### Setup (one variable) 1. Clone the repo: ```bash git clone cd muleimage ``` 2. Set **one** environment variable in `.env` — the **host** directory that contains your photo library. Whatever you point at will become your library inside Mulita. ```bash # macOS / Linux PHOTO_DIRS=/Users/you/Pictures # or any folder PHOTO_DIRS=/mnt/nas/photos # Windows (WSL) PHOTO_DIRS=/mnt/c/Users/you/Pictures ``` 3. Start the stack: ```bash docker compose up -d ``` 4. Open `http://localhost:3000`. On first boot Mulita will: - Mount your `PHOTO_DIRS` at `/photos` inside the container - Auto-create a source root called **Library** pointing at `/photos` - Queue an initial scan, generate thumbnails, and start serving them You don't need to touch `mulita.yml` or the API to get started. ### How libraries are managed Mulita is **config-driven**: the host directory you mount via `PHOTO_DIRS` becomes your library, and the backend automatically registers it as a source root on startup. There is no UI for adding or removing source roots — to change what Mulita scans, edit `.env` (or `docker-compose.yml` for multi-mount setups) and restart the stack. This keeps the model simple: **the docker mount IS the library**. No two layers, no confusion about which view to use. ### Changing or adding libraries To point at a different library: 1. Edit `PHOTO_DIRS` in `.env` 2. `docker compose down` 3. (Optional, for a clean slate) `docker volume rm muleimage_db_data muleimage_thumbs_data muleimage_proxies_data` 4. `docker compose up -d` The new library shows up automatically. Without step 3 the old library's metadata stays in the DB and you'll see a warning at startup that the old source root's path is missing on disk — that's a hint to clean up. For multiple libraries, edit `docker-compose.yml` and add additional mount lines: ```yaml volumes: - ${PHOTO_DIRS}:/photos:rw - /Volumes/Archive:/archive:rw # additional library ``` Each mounted directory will need a corresponding source root row in the DB; today that means `POST /api/v1/folders` via curl, or wait for the multi-mount auto-registration that's on the roadmap. ### Read-only libraries The default mount is `:rw` because file operations (rename, move, empty discard pile) need to mutate the filesystem. If you want a strict read-only library — pointing at a network share, an authoritative archive, etc. — flip `:rw` to `:ro` in `docker-compose.yml`. Mulita will keep working for browsing, rating, color labels, picks, heaps, and the (soft) discard flag, but the following will return an OS error: - `PATCH /photos/{id}` with a new `filename` (rename) - `POST /photos/move` (bulk move) - `DELETE /discard/empty` (file unlinks) **Heads up**: with `:rw`, Mulita has full write access to whatever host directory you mount. Treat the same way you would Lightroom's catalog folder. ## Architecture The application consists of 5 Docker services: - **frontend**: React SPA served by Nginx - **backend**: FastAPI REST API - **worker**: Celery workers for background tasks - **redis**: Message broker for Celery - **db**: SQLite database (file-based) ## Keyboard Shortcuts | Key | Action | |-----|--------| | `←` `→` `↑` `↓` | Navigate photos | | `Space` | Quick preview | | `Enter` | Open loupe view | | `P` | Pick photo | | `X` | Reject photo | | `1-5` | Set star rating | | `Tab` | Toggle left sidebar | | `I` | Toggle metadata panel | | `G` | Grid view | | `E` | Loupe view | | `Delete` | Move to trash | ## Development ### Backend Development ```bash cd backend pip install -r requirements.txt uvicorn app.main:app --reload ``` ### Frontend Development ```bash cd frontend npm install npm run dev ``` ## Configuration Source roots are managed by the UI / API (the database owns them). Edit `mulita.yml` to configure operational settings only: - Thumbnail sizes, quality, and format - Scanner behaviour (watch, batch size, initial scan) - Performance tuning (concurrency, cache TTLs, DB pool) ## Performance - Handles 100,000+ photos efficiently - Virtual scrolling for smooth timeline navigation - Thumbnail generation at 10+ photos/second - SQLite FTS5 for fast full-text search ## Future Features (Phase 2) - AI-powered scene classification - Face detection and clustering - Smart albums - Duplicate detection - Export presets - Multi-user support ## License MIT