Throw all your photos into one place — phone exports, camera dumps, WhatsApp
saves, random downloads — and before long you have thousands of files in a
single folder, full of duplicates and names like IMG-20210815-WA0007.jpg.
SnapSort brings order to that mess. It sorts the pile into a clean year/month
structure based on when each photo was actually taken, and removes duplicate
files reliably by looking at their contents.
It is a single, fast command-line tool, and it is built to be safe with an irreplaceable photo library: by default it runs as a dry run and only tells you what it would do. Nothing moves until you explicitly say so.
before/ after/
├── IMG_1234.jpg ├── 2021/
├── IMG-20210815-WA0007.jpg │ └── 08/
├── DCIM/ │ ├── IMG_1234.jpg
│ └── DSC_0001.JPG │ └── DSC_0001.jpg
├── Downloads/ ├── 2022/
│ └── photo (copy).jpg │ └── 03/
└── photo.jpg ← duplicate │ └── photo.jpg
└── (duplicate skipped)
The thing you do not want when tidying a photo archive is a tool that starts moving thousands of files on the first try. So SnapSort inverts the usual default:
- Without
--apply, SnapSort only scans, plans, and prints what it would do. It does not create, move, copy, or delete a single file. - With
--apply, it carries out exactly that plan. Even then it never overwrites an existing file — colliding names get a numeric suffix — and a failure on one file is reported without stopping the rest.
So the safe workflow is always: look at the dry run first, then apply.
# 1. See what would happen (safe, changes nothing)
snapsort ~/Pictures/unsorted
# 2. Happy with the plan? Do it for real.
snapsort ~/Pictures/unsorted --apply- Sorts into a configurable folder structure (default
YYYY/MM) using the EXIF capture date, falling back to the file's modification time. - Content-based duplicate detection — finds duplicates even when the file names differ.
- Dry run by default; changes require
--apply. - Configurable file-name pattern (e.g.
%Y-%m-%d_%H%M%S). - Never overwrites: name collisions get unique suffixes.
- Closing summary (moved / skipped / duplicates / errors).
- Optional
--copymode that leaves the originals in place.
| Option | Description |
|---|---|
<SOURCE> |
Directory to scan (required). |
-d, --dest <DIR> |
Destination root. Defaults to <source>/sorted. |
--apply |
Actually move files. Without it, SnapSort only prints the plan. |
--copy |
Copy instead of move, leaving the originals untouched. |
--dir-pattern <PATTERN> |
Folder layout. Default %Y/%m. |
--name-pattern <PATTERN> |
Rename files using a pattern. Default: keep original names. |
--ext <EXT> |
Restrict to these extensions (repeatable). Default: a built-in media list. |
-v, --verbose |
Debug-level logging (to stderr). |
Both --dir-pattern and --name-pattern understand the same date tokens:
| Token | Meaning | Example |
|---|---|---|
%Y |
year, 4 digits | 2021 |
%m |
month, zero-padded | 08 |
%d |
day, zero-padded | 15 |
%H |
hour (24h) | 14 |
%M |
minute | 30 |
%S |
second | 05 |
%% |
a literal percent | % |
For example, --name-pattern '%Y-%m-%d_%H%M%S' turns DSC_0001.JPG into
2021-08-15_143005.jpg (the original extension is kept).
SnapSort hashes the contents of every file with BLAKE3. Two files with the same hash are byte-for-byte identical, so renamed copies are still recognised — no matter how they were transferred between devices or apps. Within a group of identical files, the one with the shortest, tidiest path is kept and the rest are skipped.
$ snapsort ~/Pictures/unsorted
DRY RUN — 4 file(s) scanned under /home/me/Pictures/unsorted (pass --apply to perform)
move /home/me/Pictures/unsorted/IMG_1234.jpg -> /home/me/Pictures/unsorted/sorted/2021/08/IMG_1234.jpg
move /home/me/Pictures/unsorted/DCIM/DSC_0001.JPG -> /home/me/Pictures/unsorted/sorted/2021/08/DSC_0001.jpg
skip /home/me/Pictures/unsorted/photo.jpg (duplicate of /home/me/Pictures/unsorted/IMG_1234.jpg)
Summary: 2 to move, 1 duplicate(s) skipped, 0 without a date, 0 error(s)
With a Rust toolchain installed:
cargo install --path .or build a release binary directly:
cargo build --release
# binary at target/release/snapsortTagged releases ship prebuilt binaries for Linux, Windows and macOS, built by
CI. Grab the archive for your platform from the
Releases page, unpack it, and
put snapsort somewhere on your PATH.
- More date sources (video metadata, sidecar files).
- A
--by-daystyle layout and richer pattern tokens. - Maybe a small GUI down the line; the CLI stays the core.
MIT — see LICENSE.