A command-line tool for efficient photography workflow management. Organizes photos by capture date and synchronizes between drives.
- Import from SD: Copy (never move) photos from SD cards into organized
YYYY.MMfolders - Auto-organization: Uses EXIF capture date; falls back to file mtime, and routes truly-undated files to an
Unknown-Date/folder instead of guessing "today" - Edited versions & sidecars grouped: an original (
DSF7942.RAF/.JPG), its edits (DSF7942-1.JPG,DSF7942-HDR.heic,DSF7942 edited.jpg,DSF7942(1).png) and its sidecars (.xmp,.fp2, ...) are all filed in the same date folder as the original - Integrity-first copies: Every file is copied to a temp file, checksum-verified, then atomically renamed into place — a partial/corrupt file is never left at the destination
- No silent overwrites: A same-name file with different content is kept as
name__dup1.ext, never clobbered - Single-copy guarantee: Each physical file is copied exactly once (deduped work-list + per-destination locking), so concurrent workers can't corrupt a shared destination
- Verify before wipe: A read-only "Verify card against archive" mode confirms every file on a card has a byte-identical (checksum) copy in the archive — catching un-imported photos and silently corrupted archive copies — before you ever format the card
- Drive sync: Hardened
rsyncintegration — validates paths, refuses a--deleteagainst an empty/unmounted source, and previews deletions with a dry-run before doing anything destructive - Parallel processing: Multi-threaded copy phase for faster imports
# Install uv if you don't have it
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create the environment and install dependencies from pyproject.toml / uv.lock
uv sync
# Run the app
uv run myphotoscript.pypython -m venv .venv
source .venv/bin/activate # Linux/macOS (.venv\Scripts\activate on Windows)
pip install .- Python 3.12+
- ExifTool installed on your system (
brew install exiftool) - Rsync (for the sync functionality)
Run the script directly:
python myphotoscript.pyCopies and organizes files from an SD card to your archive:
- Sorts each file into a
YYYY.MMfolder based on its EXIF capture date - Files with no usable EXIF date fall back to the file's modification time; if even that is implausible they go to
Unknown-Date/(reported at the end so you can sort them) - Sidecars (
.xmp, ...) are filed alongside their same-stem photo - Each file is copied exactly once, checksum-verified, and written atomically
- A same-name file whose content differs is preserved as
name__dup1.ext— nothing is ever overwritten - Re-running an import is idempotent: identical files already present are skipped
- Edited versions stay with the original:
DSF7942.RAF,DSF7942-1.JPG,DSF7942-HDR.heic,DSF7942 edited.jpg,DSF7942(1).pngand their sidecars are all filed in the same date folder as the originalDSF7942— each copied exactly once (no duplication across folders)
A read-only safety check to run before formatting a card:
- Confirms every real file on the card has a byte-identical copy somewhere in the archive (checksum, not just size — a same-size corrupted copy is caught)
- Flags
MISSING(on the card but not imported),CONTENT MISMATCH(archive copy differs/corrupt),SIZE MISMATCH, andUNREADABLE(card read errors), then prints a clearSAFE TO WIPE/NOT SAFE TO WIPEverdict - Ignores hidden/AppleDouble files; nothing is ever written or deleted
- Aborts gracefully if the card disconnects mid-check
Runs from the menu, or non-interactively (scriptable; exit code 0 = safe, 1 = not safe, 2 = bad usage):
uv run myphotoscript.py verify /Volumes/SDCARD/DCIM /Volumes/ssd/imgs
uv run myphotoscript.py verify /Volumes/SDCARD/DCIM /Volumes/ssd/imgs --size-onlySynchronizes your photo library between drives, defensively:
- Validates that source and destination exist (and that the source is non-empty before any
--delete) - Option to exclude all video formats (case-insensitive), not just
.mov - Optional
--checksumfor content-based sync (repairs corrupted same-size files) --deletealways runs a--dry-runfirst and lists what would be removed, then asks for confirmation
The project includes a test suite built with pytest to ensure reliability.
uv run --with pytest pytest -qThe suite exercises the data-integrity behaviors directly (with real files, not mocks):
- Conflict safety: a same-name/different-content file is kept as
__dup1, the original is preserved - Duplicate detection: real size+checksum comparison; re-imports are idempotent
- Atomic + verified copy: checksum mismatch fails closed and leaves no partial file
- Date routing: EXIF → mtime →
Unknown-Date/; invalid dates (0000:00:00, month13) are rejected - Single-copy planning: every physical file is planned exactly once;
skip_movdrops companion videos too - Verify-before-wipe: detects missing files and same-size corrupted archive copies; ignores hidden files
- Rsync guardrails: refuses
--deleteagainst an empty source; previews deletions; passes--checksum
When adding new features, please extend the test suite with appropriate test cases.
MIT