The frontend AddSourceFolderDialog let users register source roots from inside the app, but with the bootstrap auto-creating one for the /photos mount on first boot, the dialog was redundant in the common case and confusing in every other (users had to know which container path corresponded to their host directory). Going config-driven matches Plex/Photoprism/Immich and matches the mental model "the docker mount IS the library". Frontend - Deleted components/dialogs/AddSourceFolderDialog.tsx entirely. - LeftSidebar drops the "+ Add Source Folder" button + bottom-bar layout, the addFolderMutation, the dead Plus action button on the (no-longer-existing) folders/heaps tree headers, and the Plus icon import. - api.ts: removed sourceFolders.add(), library.browse(), and the BrowseChild / BrowseResponse types. The remaining sourceFolders surface is read-only (list + manual scan). - LeftSidebar bottom strip is now just the "Scan all folders" button when there's at least one source root. Backend - Dropped POST /folders (no consumers) along with FolderCreate / FolderResponse pydantic models. The folders router header now documents the config-driven approach. - Dropped GET /library/browse (no consumers). Removed the unused os/HTTPException/SourceRoot imports it brought in. - cleanup_data_integrity now also walks the source roots and logs a warning for any whose path is missing on disk. Doesn't auto- delete (a missing path could be a temporarily unmounted drive) but surfaces enough hint to fix it. Returns the count in the summary dict alongside merged-duplicates. Docs - README "How libraries are managed" section rewritten to spell out that mounts ARE source roots, edit .env + restart, no UI for managing source roots. New "Changing or adding libraries" section walks through the typical edit-restart loop including the optional volume-nuke for a clean slate. - "Adding more libraries" subsection covers multi-mount via edited compose with a note that auto-registration is roadmap. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
198 lines
5.6 KiB
Markdown
198 lines
5.6 KiB
Markdown
# 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 <repository-url>
|
|
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 |