Skip to content

Repository files navigation

sifter

A CLI tool that catalogs photos, Live Photos, videos and audio recordings from a source folder (e.g. freshly imported from a phone) into a date-structured library like 2023/04/. Capture dates come from embedded metadata — EXIF, video container creation dates, audio tags — parsed in pure Rust, with no external tools (no exiftool, no ffmpeg) required at runtime.

Usage

sifter organize <SRC> <DEST> [options]
sifter organize --from <SRC> --to <DEST> [options]

The command scans the source, prints a pre-flight report (what will move, what is already in the library, what has no date), asks for confirmation, shows progress, and finishes with a summary.

Scanning /media/phone ... 812 media files found
Plan: organize into /home/user/Pictures using {year}/{month}
Photos 640 (12 RAW) Videos 130 Audio 18
Live Photo pairs: 22
New files to move: 771 (3.2 GiB)
Already in library: 28 (identical content, will skip)
Undated: 5 (no capture date, will skip)
Unsupported (left as-is): 8 (.pdf, .txt)
Date from: metadata 780 · filename 20 · mtime 4
Proceed with moving 771 files? [y/N]

Options

OptionDefaultDescription
-n, --dry-runoffShow the plan without changing anything
-c, --copymoveCopy files instead of moving them
-o, --on-conflict <skip|rename|overwrite|ask>skipWhat to do when a different file with the same name exists
-d, --on-no-date <skip|mtime|unsorted>skipWhat to do with files whose capture date can't be determined
-t, --template <TPL>{year}/{month}Destination directory layout
-s, --stamp-dateoffAppend the capture date to file names that don't already contain one
-r, --recursivetop level onlyScan subfolders of the source too
-y, --yesoffSkip the confirmation prompt (needed in scripts)
-v, --verbose[=FILE]offTable of every planned mapping (type, size, date source, resolution, paths); with =FILE, written to that file instead of stdout

Date resolution

  1. Embedded metadata — EXIF DateTimeOriginal/CreateDate for photos and RAW, container creation date for videos (MP4/MOV/3GP/MKV/WebM), recording-date tags for audio (ID3v2, MP4, Vorbis, RIFF).
  2. Filename patternsIMG_20230101_120000, PXL_…, IMG-20230101-WA0012, Screenshot_…, signal-2023-01-01-12-00-00, Unix timestamps, and similar.
  3. Neither found — the file is skipped (default), stamped with its filesystem mtime (--on-no-date mtime), or moved to <DEST>/unsorted/ (--on-no-date unsorted).

Date-stamped names

With --stamp-date, a file whose name carries no date of its own is renamed to <name>_YYYYMMDD_HHMMSS.<ext> on the way into the library — IMG_0001.jpg taken on 2023-04-07 12:00:00 lands as IMG_0001_20230407_120000.jpg. Names that already contain a recognizable date (IMG_20230101_120000.jpg, PXL_…, signal-2023-01-01-…) are left untouched, so nothing gets stamped twice. Live Photo pairs keep matching names, and duplicate and conflict detection both use the stamped name.

Duplicates and conflicts

If the destination already holds a file with the same name and identical content (size + BLAKE3 hash), the source is skipped as a duplicate regardless of --on-conflict — running sifter twice is safe. Only files with the same name but different content count as conflicts. Nothing is ever overwritten unless you pass --on-conflict overwrite.

Live Photos

An IMG_1234.HEIC + IMG_1234.MOV pair (same folder, same stem) is detected and kept together: the video inherits the photo's capture date, so the pair never splits across a month boundary, and a conflict rename keeps both stems identical.

Templates

Tokens: {year}{month}{day}{year_short}{month_name}{type} (Photos/Videos/Audio). / separates directories. Examples:

--template "{year}/{month}" # 2023/04 (default)
--template "{year}/{year}-{month}-{day}" # 2023/2023-04-07
--template "{type}/{year}/{month_name}" # Photos/2023/April

Exit codes

0 success · 1 error · 2 aborted by user · 3 finished with per-file errors

Building

cargo build --release

All dependencies are pure Rust, so a fully static binary is one target away:

cargo build --release --target x86_64-unknown-linux-musl

Development

cargo test runs the unit suite plus end-to-end tests that exercise the binary against the tiny committed media fixtures in tests/fixtures/ (generated once with ffmpeg and a small Python script — those tools are not needed to build, test, or run sifter).

License

GPL-3.0-or-later

About

Catalog photos, videos and audio into a date-structured library by capture date

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages