Guidance for Claude Code and any AI agent working in this repository.
The single most important rule in this project. Non-negotiable.
The whole reason this project exists is to remove duplicates, and that part works great. Do NOT change, "improve", refactor, or reorganize the duplicate-detection/removal logic under any circumstances unless the user explicitly asks you to and confirms.
It is MD5 content hashing that relocates duplicates into a separate folder (it never
deletes them). The protected code lives in main.py:
get_file_hash— MD5 hashing (streamed, with optional SQLite cache)detect_duplicates_optimized— groups duplicate inputs by hashbuild_target_file_hashes— hashes files already in the target (skip-if-exists)build_additional_duplicates_hashes— hashes extra check dirshandle_duplicate_thread_safe/ the "already exists" / "additional duplicate" handlers — relocate duplicates intotarget_duplicates(suffixed-duplicate-N,_already_exists_N, etc.)
Before ANY change, confirm it does not touch hashing or duplicate grouping/relocation. Folder-layout tweaks to the duplicates output tree are acceptable; the detection mechanism must stay intact.
There are no automated tests guarding this — so be doubly careful, and verify with a dry_run
on a small fixture before/after any nearby change.
Organizes a photo/video library by: (1) detecting duplicates (RULE 1) and relocating them aside, and (2) copying or moving the rest into a dated folder tree:
target/YYYY/MM-MonthName/ e.g. 2024/04-April/
target/YYYY/MM-MonthName/DD/ only when a single day has >= pictures_folder_per_day photos (default 40)
The pictures_folder_per_day threshold keeps multi-day trips together in the month folder
and only pulls genuinely dense single days into their own subfolder.
| File | Purpose |
|---|---|
main.py (PhotoOrganizer) |
The engine: organize, dedup, dates, GPU/multiprocessing, hash cache. ALL real logic. |
repair_dates.py |
Standalone date repair script for already-organized libraries (see below). |
config.json |
Working config (personal paths). |
config.example.json |
Full reference config with all options set to their defaults. |
DEVELOPERS.md |
Technical architecture notes (processing pipeline, design patterns). |
There is no UI — this is a pure CLI tool.
python main.py -y config.json # -y auto-confirms; config path is positionalAlways set "dry_run": true first on real data, eyeball where files would land, then run for real.
Configured by date_source + date_priority in options:
- Recommended:
date_source: "oldest"+date_priority: "exif_first"— prefer the embedded capture date when present, else the filesystem date; witholdestit takesmin(metadata, filesystem), which self-corrects against filesystem dates pushed forward by copies/backups. - Embedded capture date by type (via
extract_capture_datetimeinmain.py): images (JPEG/TIFF/HEIC) → EXIFDateTimeOriginal; videos → ffprobecreation_time(converted from UTC to local); then the renamer filename (img_/vid_YYYY_MM_DD_..., viadate_from_filename) as a last resort for any type, including PNGs. ffprobe (ffmpeg) is an optional system binary — if it's missing, videos fall back to filename/filesystem date. - Stamping the output:
set_modified_to_capture_date(defaulttrue) writes the computed capture date onto each organized destination file's mtime. It only ever touches destination files (safe in copy mode) and reuses the date already computed for foldering. Set itfalseto keep the source's original mtime instead. Foldering uses the capture date regardless of this flag. - Linux note:
st_ctimeis inode-change time, NOT creation time. The code uses realst_birthtimewhen available and otherwise falls back tost_mtime— it must never usest_ctimeas a "created"/"oldest" date. Seefs_date_from_stat/get_creation_timestampinmain.py. - Reading EXIF is wrapped in try/finally so it can never alter a source file's timestamps.
synchronize_file_datesis a no-op in copy mode — it must never mutate originals when copying.
Repairs photos and videos in an already-organized library whose filesystem dates were corrupted
(pushed forward). For each file it derives the true capture date (EXIF for
images, ffprobe creation_time for videos, then the renamer-filename img_/vid_YYYY_MM_DD_HH_MM_SS_...
fallback — same chain as main.py) and, if the current mtime is newer than the true date (beyond
a small tolerance), resets mtime + atime to the true capture date via os.utime.
python repair_dates.py <directory> # DRY RUN preview (default)
python repair_dates.py <directory> --apply # actually fix dates
python repair_dates.py <directory> --apply --verbose
python repair_dates.py <directory> --rename --apply # fix dates AND rename to main.py's format--rename (optional): after fixing a file's date, also renames it to the canonical
{img_|vid_}YYYY_MM_DD_HH_MM_SS_microsec_size.ext format produced by main.py's
_generate_renamed_filename (faithfully ported as renamed_filename/sanitize_filename in
repair_dates.py). It runs for every file with a parseable capture date (not just date-fixed ones),
skips files already in that format (no churn — idempotent), and skips files with no parseable date.
Like the date fix, it previews in a dry run and only executes with --apply. Collisions are
resolved with a _N suffix; nothing is overwritten.
Linux limitation: os.utime only sets mtime/atime; birth/creation time cannot be changed from
userspace. mtime is what the organizer reads for filing, so this is sufficient. It does not write
EXIF tags. It does not touch duplicate logic.
- Match the surrounding style. No new dependencies without asking. Current deps:
Pillow,pillow-heiffor HEIC/HEIF,tqdm; GPU backends optional;ffprobe/ffmpegoptional system binary for video capture dates — degrades gracefully if absent. Seerequirements.txt. - Console feedback: use
print(...)/tqdmfor anything users must see —self.log(...)is silenced unlessverbose_loggingis true. Long directory walks should always show progress. - Verify changes with a
dry_run/--dry-runon a small fixture; confirm duplicate counts are unchanged (RULE 1). - For technical architecture details (processing phases, design patterns, GPU backends), see
DEVELOPERS.md.