The first phase-11 file op (inline rename) returns EROFS today because docker-compose mounts ~/Pictures read-only by default. Lightroom-style file operations (rename, move, discard-pile empty) all need to mutate the filesystem, so the right default is :rw. Flips both the backend and worker mounts to :rw with an inline comment explaining the trade-off, and adds a "Photo directory mounts and permissions" section to the README that: - States the default is now :rw - Explains exactly which endpoints fail under :ro (rename, empty discard pile, future move/copy) - Notes the implication: Mulita has full write access to whatever host directory ends up at /host/Pictures, same trust model as Lightroom's catalog folder Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
153 lines
4.1 KiB
Markdown
153 lines
4.1 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
|
|
- Photo directories to mount
|
|
|
|
### Setup
|
|
|
|
1. Clone the repository:
|
|
```bash
|
|
git clone <repository-url>
|
|
cd muleimage
|
|
```
|
|
|
|
2. Configure your photo directories in `.env`:
|
|
```bash
|
|
# Edit .env file
|
|
PHOTO_DIRS=/path/to/your/photos
|
|
```
|
|
|
|
3. Start the application:
|
|
```bash
|
|
docker-compose up -d
|
|
```
|
|
|
|
4. Access the application at `http://localhost:3000`
|
|
|
|
### Photo directory mounts and permissions
|
|
|
|
Mulita is a Lightroom-style manager — file operations (rename, move,
|
|
discard, empty discard pile) need to mutate the filesystem under your
|
|
photo mounts. The default `docker-compose.yml` mounts:
|
|
|
|
- `${PHOTO_DIRS}` → `/photos` (read-write)
|
|
- `~/Pictures` → `/host/Pictures` (**read-write** by default so file
|
|
operations work on your system Pictures folder out of the box)
|
|
|
|
If you want a strict read-only library — for example pointing at a
|
|
network share or your authoritative archive — change `:rw` to `:ro`
|
|
on the mount in `docker-compose.yml`. Mulita will keep working for
|
|
browsing, rating, color labels, picks, heaps, and the discard flag,
|
|
but the following endpoints will return an error from the OS
|
|
(`EROFS` / `Read-only file system`):
|
|
|
|
- `PATCH /photos/{id}` with a new `filename` (rename)
|
|
- `DELETE /discard/empty` (file unlinks)
|
|
- Future move / copy endpoints
|
|
|
|
**Heads up**: with `:rw`, Mulita has full write access to whatever
|
|
host directory you mount under `~/Pictures`. 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
|
|
|
|
Edit `mulita.yml` to configure:
|
|
- Source photo directories
|
|
- Thumbnail sizes and quality
|
|
- Scanner settings
|
|
- Performance tuning
|
|
|
|
## 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 |