Digikam Nextcloud Sync Tools
Find a file
2026-07-22 13:09:00 +02:00
digikam_nextcloud feat: sessions 2026-07-22 13:09:00 +02:00
.gitignore feat: sessions 2026-07-22 13:09:00 +02:00
config.example.yaml feat: sessions 2026-07-22 13:09:00 +02:00
LICENSE Initial commit 2026-07-21 13:42:39 +00:00
README.md feat: sessions 2026-07-22 13:09:00 +02:00
requirements.txt initial 2026-07-21 15:59:34 +02:00
sync_faces.py intermediate update 2026-07-21 16:21:29 +02:00

digikam-nextcloud

One-way sync of face regions from digiKam → Nextcloud Recognize.

digiKam is the source of truth. Nothing is written back to digiKam.

Layout

digikam_nextcloud/          # Python package
  cli.py                    # argparse / main
  sync.py                   # orchestration
  digikam.py                # digiKam SQLite reader
  nextcloud_db.py           # SQL backend (+ SSH tunnel)
  nextcloud_http.py         # WebDAV / Recognize DAV backend
  matching.py               # file + region matching
  models.py                 # dataclasses + backend protocol
  session_state.py          # cancel/resume session files
  …
sync_faces.py               # thin CLI wrapper
config.example.yaml
python sync_faces.py --config config.yaml
# or
python -m digikam_nextcloud --config config.yaml

What it does

  1. Reads named face rectangles from digiKams digikam4.db
  2. Matches those files to Nextcloud
  3. Compares regions with Recognize faces via IoU
  4. Assigns overlapping faces to digiKam people
  5. Optionally inserts digiKam-only regions (DB backend only)
  6. Reports conflicts when the same box has different names

Default mode is dry-run. Pass --apply to write.

Backends

Default / preferred: http (WebDAV + Recognize DAV with an app password).

Backend How it connects Insert new face boxes Assign / rename people
http (default) Nextcloud WebDAV + Recognize DAV No Yes (MOVE / MKCOL)
db SQL (+ optional SSH tunnel) Yes Yes

HTTP / WebDAV (preferred)

Tuned for large libraries (keep-alive connections, concurrent workers, targeted PROPFIND only on digiKam-faced images — no full tree scan):

  • PROPFIND Depth:0 on each mapped file path + nc:face-detections
  • MKCOL people under /remote.php/dav/recognize/{user}/faces/{Person}
  • MOVE detections into person collections
export NEXTCLOUD_PASSWORD='your-app-password'
python sync_faces.py --config config.yaml \
  --http-workers 24 --batch-size 500 --no-insert-missing

Configure path_maps so digiKam filesystem paths become Nextcloud paths (e.g. /home/you/Nextcloud/Photos/…Photos/…).

Recognize 403 / X-Recognize-Api-Key

Modern Recognize requires a server-generated API key for /remote.php/dav/recognize/… (create people, MOVE/assign faces).

This tool will try to auto-fetch the key from the Photos app page (initial-state-photos-recognizeApiKey, valid ~24h). That needs the Photos app enabled.

If you still get 403:

# Admin: turn off the gate (simplest for self-hosted)
occ config:app:set recognize require_api_key --value false

# Or pass a key (from Photos page HTML, expires ~24h)
export RECOGNIZE_API_KEY='…'
# config: recognize_api_key: "…"

HTTP limits

  • Cannot create new face rectangles → run Recognize detect first, then assign
  • Unclustered detections (cluster_id NULL) are not MOVE-able until clustered
  • Use a Nextcloud app password

DB over SSH (optional)

When you need inserts or raw SQL access:

python sync_faces.py --config config.yaml --backend db \
  --ssh-host nextcloud-server.example.com \
  --ssh-user cliff \
  --ssh-identity ~/.ssh/id_ed25519

Requirements

  • Python 3.10+
  • digiKam SQLite core DB (digikam4.db)
  • OpenSSH client (for SSH tunnel)
  • DB drivers as needed: PyMySQL / psycopg2-binary
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Configuration

cp config.example.yaml config.yaml
# edit digikam_db, backend, DSN or URL, nc_user, path_maps, ssh

Path matching

digiKam path Nextcloud path path_map
/mnt/nas/Photos/2024/a.jpg files/Photos/2024/a.jpg from: /mnt/nas/Photosto: Photos

Fallback: unique filename + file size.

Coordinates

System Box units
digiKam tagRegion Pixels
Recognize Relative 01

Usage

# Dry-run
python sync_faces.py --config config.yaml

# Write
python sync_faces.py --config config.yaml --apply

# JSON report
python sync_faces.py --config config.yaml --json-out report.json

# Only name existing detections
python sync_faces.py --config config.yaml --no-insert-missing --apply

# Report conflicts without overwriting NC names
python sync_faces.py --config config.yaml --leave-conflicts

# Single person only (tests / quick updates — skips full-library scan)
python sync_faces.py --config config.yaml --only-person Alice
python sync_faces.py --config config.yaml --only-person Alice --apply

# Cancel / resume (large libraries)
# Every run creates a session file under .sync_sessions/ and prints the id.
# Ctrl+C stops after the current image; re-run with --resume to continue.
python sync_faces.py --config config.yaml --apply
# … cancel with Ctrl+C …
python sync_faces.py --config config.yaml --apply --resume 20260722-120000-abc123
python sync_faces.py --list-sessions
python sync_faces.py --config config.yaml --apply --no-session   # opt out

Exit codes

Code Meaning
0 Success, no conflicts
1 Completed with conflicts reported
2 Usage / config error
130 Cancelled (SIGINT/SIGTERM); session saved for --resume

Conflict policy

Overlapping boxes (IoU ≥ threshold) with different person names.

Default: conflict is reported and digiKam wins on --apply.

--leave-conflicts: report only; leave Nextcloud unchanged.

Large libraries (100k500k+ faces)

The pipeline is batched so memory stays roughly O(batch_size), not O(total faces).

Setting Default Notes
backend http Preferred
http_workers 16 Concurrent keep-alive WebDAV; try 2432 if the server allows
batch_size 500 digiKam images per batch (memory ≈ O(batch))
insert_missing false at scale Assign names only
limit_images none Smoke-test subset
only_person / --only-person none One digiKam person only (fast tests / targeted updates)
Session / --resume on (.sync_sessions/) Skip already-processed image ids after cancel
Report detail capped (~2000 actions) Counters still cover the full run

Cancel / resume

Long runs write progress to .sync_sessions/<session-id>.json (override with state_dir / --state-dir / env DIGIKAM_NEXTCLOUD_STATE_DIR).

Flag Meaning
(default) Auto-create a session id; print it at start
--session-id ID Use this id (create or resume if the file exists)
--resume ID Resume an existing session (error if missing)
--list-sessions Show sessions and progress
--no-session Do not persist progress

On Ctrl+C, the current image finishes, state is flushed, status becomes cancelled, and exit code 130 is returned. Resume skips digiKam image ids already recorded in the session file.

# Smoke test
python sync_faces.py --config config.yaml --limit-images 200 -v

# One person only (targeted index — no full library scan)
python sync_faces.py --config config.yaml --only-person Alice -v

# Full assign-only over HTTP (preferred)
python sync_faces.py --config config.yaml \
  --no-insert-missing --batch-size 500 --http-workers 24 --apply

Progress looks like:

INFO digiKam totals: 180000 images with face tags, 542098 tagRegion rows
INFO Progress: 5000 / 180000 images (2.8%), … 40.0 img/s, ETA 4375s

Safety

  • Dry-run by default
  • digiKam DB opened read-only
  • Skips Unknown / Unconfirmed / Ignored
  • One-way only

License

See LICENSE.