Photo Date Editor
A self-hosted browser UI for manually dating scanned photographs. The browser receives temporary read/write access to a local Chromebook or computer folder, sends one JPEG at a time to the Docker backend for ExifTool processing, and overwrites the same local file after processing.
Current scope
- JPG and JPEG
- Local directory picker in Chrome/Edge
- Three-column photo workflow
- Exact date, month/year, year-only, approximate year
- Optional time; unknown time defaults to noon
- Description and keywords
- In-place overwrite: no browser download and no
_originalfile - Saved/pending/failed state stored in browser local storage
- Keyboard navigation
Requirements
- Docker with Docker Compose
- A current Chromium browser such as Chrome or Edge
- HTTPS when accessed from another device
- A backup of irreplaceable scans before testing any metadata editor
The browser File System Access API is required. Firefox and Safari are not supported by this first version.
Install
cp .env.example .env
nano .env
docker compose up -d --build
Example .env:
APP_URL=https://photos.example.internal
APP_PORT=8080
APP_NAME=Photo Date Editor
MAX_UPLOAD_MB=150
LOG_LEVEL=INFO
Open the configured HTTPS URL through Caddy, click Open photo folder, and grant read/write access.
Caddy example
Caddy may run in another container or host. Proxy to the Docker host and exposed port:
photos.example.internal {
@lan remote_ip private_ranges
handle @lan {
reverse_proxy 192.168.1.20:8080
}
respond 403
}
The browser directory picker requires a secure context. Use a certificate trusted by the Chromebook. A real domain with internal DNS is usually easier than deploying a private CA certificate to every family device.
What happens when Save is clicked
- The browser reads the selected local JPEG.
- It sends the file and entered fields to
/api/process. - ExifTool modifies a temporary file inside the container using
-overwrite_original. - The backend returns the modified JPEG.
- Chrome writes the returned bytes over the selected original file with
createWritable(). - The backend temporary directory is deleted automatically.
No second photo is intentionally left on the Chromebook or Docker host.
Date behavior
EXIF date fields require a complete timestamp:
| Selected precision | EXIF value | XMP DateCreated |
|---|---|---|
| Exact date | Chosen date | YYYY-MM-DD |
| Month and year | First day of month | YYYY-MM |
| Year only | January 1 at noon | YYYY |
| Approximate year | January 1 at noon | YYYY |
The chosen precision is also written into XMP Photoshop Instructions.
Keyboard shortcuts
EnterorCtrl+Enter: Save & NextCtrl+S: Save without movingLeft/Right: Previous / next photo when not typingS: Skip when not typingC: Copy previous values when not typing
Important limitations
- The app cannot silently access a Chromebook folder. The user must choose it and approve read/write access.
- Browser permission may need to be granted again after closing/reopening the browser.
- Overwriting a file through the browser generally changes its filesystem modified time to the time of the save. EXIF/XMP photo dates are independent of that filesystem timestamp.
- Preview rotation is visual only in this MVP; it does not rotate image pixels or write orientation metadata.
- Test with copies first, then use your own normal backup routine for the originals.
Planned format expansion
The backend already uses ExifTool, which is a good foundation for later format support. The next likely steps are:
- HEIC/HEIF preview and metadata round-trip testing on ChromeOS.
- Canon RAW (
CR2,CR3) and Nikon RAW (NEF,NRW). - Sidecar XMP mode for RAW files, because modifying proprietary RAW containers directly is a different risk profile from JPEG.
- Optional server-side folder mode for photos stored on NAS-mounted storage.