Skip to content

Latest commit

 

History

History
126 lines (96 loc) · 6.81 KB

File metadata and controls

126 lines (96 loc) · 6.81 KB

Photo Organizer — Agent / Claude Code Context

Guidance for Claude Code and any AI agent working in this repository.


RULE 1 — NEVER TOUCH THE DUPLICATE-DETECTION LOGIC

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 hash
  • build_target_file_hashes — hashes files already in the target (skip-if-exists)
  • build_additional_duplicates_hashes — hashes extra check dirs
  • handle_duplicate_thread_safe / the "already exists" / "additional duplicate" handlers — relocate duplicates into target_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.


What this tool does

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.

Layout

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.

How to run

python main.py -y config.json        # -y auto-confirms; config path is positional

Always set "dry_run": true first on real data, eyeball where files would land, then run for real.

Dates (how a photo's date is decided)

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; with oldest it takes min(metadata, filesystem), which self-corrects against filesystem dates pushed forward by copies/backups.
  • Embedded capture date by type (via extract_capture_datetime in main.py): images (JPEG/TIFF/HEIC) → EXIF DateTimeOriginal; videos → ffprobe creation_time (converted from UTC to local); then the renamer filename (img_/vid_YYYY_MM_DD_..., via date_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 (default true) 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 it false to keep the source's original mtime instead. Foldering uses the capture date regardless of this flag.
  • Linux note: st_ctime is inode-change time, NOT creation time. The code uses real st_birthtime when available and otherwise falls back to st_mtime — it must never use st_ctime as a "created"/"oldest" date. See fs_date_from_stat / get_creation_timestamp in main.py.
  • Reading EXIF is wrapped in try/finally so it can never alter a source file's timestamps.
  • synchronize_file_dates is a no-op in copy mode — it must never mutate originals when copying.

repair_dates.py — Date Repair Script

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.

Conventions for editing

  • Match the surrounding style. No new dependencies without asking. Current deps: Pillow, pillow-heif for HEIC/HEIF, tqdm; GPU backends optional; ffprobe/ffmpeg optional system binary for video capture dates — degrades gracefully if absent. See requirements.txt.
  • Console feedback: use print(...) / tqdm for anything users must see — self.log(...) is silenced unless verbose_logging is true. Long directory walks should always show progress.
  • Verify changes with a dry_run/--dry-run on a small fixture; confirm duplicate counts are unchanged (RULE 1).
  • For technical architecture details (processing phases, design patterns, GPU backends), see DEVELOPERS.md.