Initial commit: Fuji photo processor pipeline

Automatic photo processing: Fuji X-H2 → FTP → Synology NAS → resize → Immich
- PollingObserver watches /incoming/ for new JPEGs
- Moves originals to /originals/YYYY/MM/
- Creates resized copies (1080x1920 @ 85%) with EXIF preserved
- SQLite tracking to prevent duplicate processing
- Deploy script for Synology NAS (docker run)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Nick Roodenrijs
2026-03-08 18:38:18 +01:00
commit b9b5f53e53
6 changed files with 488 additions and 0 deletions

111
README.md Normal file
View File

@@ -0,0 +1,111 @@
# Fuji Photo Processor
Automatic photo processing pipeline for Fuji X-H2 photos: camera uploads via FTP to Synology NAS, files are automatically resized and organized, then picked up by Immich for library management.
## Architecture
```
┌─────────────┐ FTP ┌──────────────────┐ watchdog ┌─────────────┐
│ Fuji X-H2 │ ──────────→ │ Synology NAS │ ────────────→ │ Processor │
│ (camera) │ port 21 │ /volume2/photos/ │ polling │ container │
└─────────────┘ │ incoming/ │ └──────┬──────┘
└──────────────────┘ │
│ resize + move
┌─────────────┴─────────────┐
│ │
┌─────▼─────┐ ┌───────▼───────┐
│ /originals │ │ /processed │
│ YYYY/MM/ │ │ YYYY/MM/ │
│ (full-res) │ │ (1080px max) │
└────────────┘ └───────┬───────┘
┌───────▼───────┐
│ Immich │
│ external lib │
└───────────────┘
```
## Prerequisites
- Synology NAS with Docker (Container Manager) installed
- FTP server enabled on DSM
- Immich running (for photo management)
## Setup
### 1. FTP Server (DSM)
1. Open **Control Panel → File Services → FTP**
2. Enable FTP service on port **21**
3. Set passive port range: **50000-50100**
4. Create a dedicated user `fujiftp` with write access to `/volume2/photos/incoming`
### 2. Camera Configuration (Fuji X-H2)
Configure an FTP profile on the camera:
| Setting | Value |
|-----------------|--------------------------|
| Server IP | `192.168.175.141` |
| Port | `21` |
| Passive Mode | **ON** |
| Username | `fujiftp` |
| Password | *(your password)* |
| Upload Dir | `/incoming` |
| Auto Transfer | ON (or manual trigger) |
### 3. Deploy
```bash
./deploy.sh
```
The deploy script will:
- Create required directories on the NAS
- Transfer and build the Docker image on the NAS
- Start the container with appropriate volume mounts
### 4. Immich Integration
1. In Immich, go to **Administration → External Libraries**
2. Add a new library with import path: `/volume2/photos/processed`
3. Set scan interval (e.g., every 15 minutes)
4. Mount `/volume2/photos/processed` into the Immich container as a read-only volume
## Container Details
### Volumes
| Container Path | Host Path | Purpose |
|----------------|----------------------------------------|---------------------------|
| `/incoming` | `/volume2/photos/incoming` | FTP upload landing zone |
| `/originals` | `/volume2/photos/originals` | Full-resolution originals |
| `/processed` | `/volume2/photos/processed` | Resized copies for Immich |
| `/data` | `/volume2/docker/photo-processor/data` | SQLite tracking database |
### Environment Variables
| Variable | Default | Description |
|-----------------|---------|--------------------------------------|
| `POLL_INTERVAL` | `30` | Filesystem poll interval in seconds |
| `JPEG_QUALITY` | `85` | JPEG compression quality (1-100) |
| `MAX_WIDTH` | `1080` | Maximum width for resized images |
| `MAX_HEIGHT` | `1920` | Maximum height for resized images |
| `TZ` | `Europe/Amsterdam` | Container timezone |
## Troubleshooting
### Check container logs
```bash
ssh -i ../SynologyDocker/synology_ssh_key ssh@192.168.175.141 \
'sudo /usr/local/bin/docker logs -f photo-processor'
```
### Common Issues
- **Files not detected**: Check that the FTP user has write permissions to `/volume2/photos/incoming`. The processor uses polling (not inotify) so there may be up to a 30-second delay.
- **Permission denied on originals/processed**: Ensure the directories exist and are writable. The deploy script creates them automatically.
- **Duplicate filenames**: The processor tracks files by filename in SQLite. If you re-upload a file with the same name, it will be skipped. Delete the entry from `/volume2/docker/photo-processor/data/processed.db` to reprocess.
- **Container keeps restarting**: Check logs for Python errors. Common cause: missing directories or permission issues on volume mounts.
- **EXIF data lost**: The processor preserves EXIF data from the original. If EXIF is missing, the original file may not have contained it (check camera settings).