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 _original file
  • 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

  1. The browser reads the selected local JPEG.
  2. It sends the file and entered fields to /api/process.
  3. ExifTool modifies a temporary file inside the container using -overwrite_original.
  4. The backend returns the modified JPEG.
  5. Chrome writes the returned bytes over the selected original file with createWritable().
  6. 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

  • Enter or Ctrl+Enter: Save & Next
  • Ctrl+S: Save without moving
  • Left / Right: Previous / next photo when not typing
  • S: Skip when not typing
  • C: 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:

  1. HEIC/HEIF preview and metadata round-trip testing on ChromeOS.
  2. Canon RAW (CR2, CR3) and Nikon RAW (NEF, NRW).
  3. Sidecar XMP mode for RAW files, because modifying proprietary RAW containers directly is a different risk profile from JPEG.
  4. Optional server-side folder mode for photos stored on NAS-mounted storage.
S
Description
No description provided
Readme
709 KiB
Languages
JavaScript 38.7%
Python 36.9%
CSS 11.7%
HTML 11.5%
Dockerfile 1.2%