Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

fotofolder

fotofolder is a Python CLI for copying photo and video archives into a deterministic, date-based folder layout. It keeps a SQLite manifest, extracts metadata with ExifTool, hashes source files, writes review reports, and leaves the source tree untouched.

This is intended for cautious archive organization: run it against a source directory, inspect the reports, then repeat as more source folders are added.

Features

  • Copies files instead of moving or modifying originals.
  • Writes each destination through a temporary file before atomically renaming it.
  • Never overwrites an existing destination file.
  • Uses SHA-256 to detect exact duplicates across the full manifest.
  • Uses metadata dates first, filename dates next, and filesystem timestamps only as a low-confidence fallback.
  • Keeps matched sidecars with their primary media file.
  • Sends unmatched sidecars and ambiguous files to review reports.
  • Supports reruns without redoing unchanged metadata, hash, copy, and verify work.

Requirements

  • Python 3.11 or newer.
  • ExifTool on the host when running the Python CLI directly.
  • Docker, only if using the container workflow.

On macOS, ExifTool can be installed with Homebrew:

brew install exiftool

Setup

Create a local virtual environment and install the package in editable mode:

python3 -m venv .venv
.venv/bin/python -m pip install --upgrade pip
.venv/bin/python -m pip install -e .

Run the tests:

make test

Quick Start

Use run for normal operation. Each invocation creates a run record, processes one source directory, and writes timestamped reports with both per-run deltas and full-manifest totals.

mkdir -p work

.venv/bin/python -m fotofolder run \
  --source /path/to/source-photos \
  --dest /path/to/organized-photos \
  --db work/manifest.sqlite \
  --reports work/reports

Useful options:

--dry-run                 Stop after scan, metadata, hash, plan, and report.
--metadata-workers 2      Metadata extraction worker count.
--hash-workers 2          Hash worker count.
--copy-workers 2          Copy worker count.
--verify-workers 2        Verify worker count.
--quiet                   Suppress progress logs on stderr.

For a no-copy preview:

.venv/bin/python -m fotofolder --quiet run \
  --source /path/to/source-photos \
  --dest /path/to/organized-photos \
  --db work/manifest.sqlite \
  --reports work/reports \
  --dry-run

Output Layout

Primary media is planned into date folders. The destination filename includes a short content hash suffix so same-name files with different contents can coexist.

organized-photos/
  2024/
    2024-07/
      2024-07-12/
        IMG_1234__a1b2c3d4.heic

Files needing manual review are planned under _needs_review/ instead of being guessed into the main archive layout.

Reports

Reports are written to a timestamped run directory:

work/reports/
  20260429T212524Z_run-2/
    summary.txt
    copy_plan_delta.jsonl
    copy_plan_total.jsonl
    duplicates_delta.jsonl
    duplicates_total.jsonl
    needs_review_delta.jsonl
    needs_review_total.jsonl
    unmatched_sidecars_delta.jsonl
    unmatched_sidecars_total.jsonl
    copy_errors_delta.jsonl
    copy_errors_total.jsonl
    verify_errors_delta.jsonl
    verify_errors_total.jsonl

*_delta.jsonl files and the [delta] summary section describe the current run invocation. *_total.jsonl files and the [totals] summary section describe the full SQLite manifest across all runs and source directories.

Reruns

The SQLite database is the durable manifest. Reruns are optimized for adding new files to one or more source directories over time.

  • Scan compares each source path's size and filesystem modified timestamp to the last observed values.
  • If a file is unchanged, existing hash and metadata records are reused.
  • If a file changed, derived records for that file are invalidated so it can be hashed, planned, copied, and verified again.
  • If a file is already copied and verified for the current planned destination and source hash, copy and verify skip it.
  • To force a complete rebuild, delete the SQLite database and run from the beginning.

Phase Commands

The individual phase commands operate on the manifest as a whole. They are useful for debugging or manually stepping through a run, but per-run delta reports are clearest when using run.

.venv/bin/python -m fotofolder scan --source /path/to/source-photos --db work/manifest.sqlite
.venv/bin/python -m fotofolder metadata --db work/manifest.sqlite
.venv/bin/python -m fotofolder hash --db work/manifest.sqlite --hash-workers 1
.venv/bin/python -m fotofolder plan --db work/manifest.sqlite --dest /path/to/organized-photos
.venv/bin/python -m fotofolder copy --db work/manifest.sqlite --copy-workers 1
.venv/bin/python -m fotofolder verify --db work/manifest.sqlite --verify-workers 1
.venv/bin/python -m fotofolder report --db work/manifest.sqlite --out work/reports

Logging

Verbose progress logs are enabled by default and are written to stderr. The final command summary remains JSON on stdout. Use --quiet when stdout must contain only JSON.

Common actions include:

  • scan source_added, source_unchanged, source_changed
  • hash skip_already_hashed
  • metadata skip_metadata_cached
  • plan plan_copy, plan_sidecar, plan_unmatched_sidecar
  • copy skip_already_copied

Docker

The Docker image includes ExifTool.

make docker-build
docker run --rm fotofolder:latest --version

Run with bind-mounted source, destination, and work directories. Pass container paths to fotofolder, not host paths.

mkdir -p work

docker run --rm \
  -v /host/source-photos:/source:ro \
  -v /host/organized-photos:/dest \
  -v "$PWD/work:/work" \
  fotofolder:latest run \
  --source /source \
  --dest /dest \
  --db /work/manifest.sqlite \
  --reports /work/reports

Export an image tarball when needed:

make docker-save

Generated tarballs are intentionally ignored by Git.

Development

make test
make docker-smoke

The implementation design notes are in docs/python-photo-organizer-design.md.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages