{"id":51306481,"url":"https://github.com/chicogong/snaptidy","last_synced_at":"2026-07-01T00:02:40.027Z","repository":{"id":364951834,"uuid":"1269867531","full_name":"chicogong/snaptidy","owner":"chicogong","description":"AI-powered photo \u0026 video organizer for macOS. Deduplicate, tidy up, and restructure your library - safely, through conversation.","archived":false,"fork":false,"pushed_at":"2026-06-15T08:00:25.000Z","size":48,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-15T09:12:00.120Z","etag":null,"topics":["agents-md","ai-skill","claude-code","cursor","deduplication","macos","openclaw","perceptual-hash","photo-management","photo-organizer","python","skill","windsurf","workbuddy"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chicogong.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-15T07:07:17.000Z","updated_at":"2026-06-15T08:00:35.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/chicogong/snaptidy","commit_stats":null,"previous_names":["chicogong/snaptidy"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/chicogong/snaptidy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chicogong%2Fsnaptidy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chicogong%2Fsnaptidy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chicogong%2Fsnaptidy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chicogong%2Fsnaptidy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chicogong","download_url":"https://codeload.github.com/chicogong/snaptidy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chicogong%2Fsnaptidy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34987611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-30T02:00:05.919Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agents-md","ai-skill","claude-code","cursor","deduplication","macos","openclaw","perceptual-hash","photo-management","photo-organizer","python","skill","windsurf","workbuddy"],"created_at":"2026-07-01T00:02:38.973Z","updated_at":"2026-07-01T00:02:40.019Z","avatar_url":"https://github.com/chicogong.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SnapTidy\n\n[English](README.md) | [简体中文](README.zh-CN.md)\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)\n[![Python 3.9+](https://img.shields.io/badge/Python-3.9+-blue.svg?style=flat-square)](https://www.python.org/downloads/)\n[![macOS](https://img.shields.io/badge/Platform-macOS-black.svg?style=flat-square)](https://www.apple.com/macos)\n[![AI Skill](https://img.shields.io/badge/AI-Skill-purple.svg?style=flat-square)](https://github.com/topics/ai-skill)\n[![CI](https://img.shields.io/github/actions/workflow/status/chicogong/snaptidy/ci.yml?branch=main\u0026label=CI\u0026style=flat-square)](https://github.com/chicogong/snaptidy/actions/workflows/ci.yml)\n[![Version](https://img.shields.io/badge/Version-3.14.3-green.svg?style=flat-square)](https://github.com/chicogong/snaptidy)\n[![Website](https://img.shields.io/badge/Website-realtime--ai.chat-blue.svg?style=flat-square)](https://realtime-ai.chat/snaptidy/)\n\n\u003e AI-powered photo \u0026 video organizer for macOS. Deduplicate photos, find similar images via perceptual hashing (pHash) and Apple ML vectors, fix EXIF metadata, and restructure your library — safely, through natural-language conversation. Zero-risk, read-only scan with human-approved actions. Open source, MIT licensed.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://realtime-ai.chat/snaptidy/screenshots/landing-page.png\" alt=\"SnapTidy Landing Page\" width=\"800\"\u003e\n\u003c/p\u003e\n\n## How SnapTidy Compares\n\n| Feature | SnapTidy | Commercial Apps | Basic CLI Tools |\n|---------|----------|----------------|-----------------|\n| AI conversation-driven | ✓ | ✗ | ✗ |\n| Zero-install core (stdlib only) | ✓ | ✗ | ~ |\n| Perceptual hash (pHash) similarity | ✓ | ~ | ~ |\n| Apple ML feature vector detection | ✓ | ✗ | ✗ |\n| Cross-format dedup (HEIC ↔ JPEG) | ✓ | ~ | ✗ |\n| Scaled duplicate detection | ✓ | ✗ | ✗ |\n| Burst photo detection (SubSecTime) | ✓ | ✗ | ✗ |\n| EXIF metadata extraction \u0026 editing | ✓ | ~ | ✗ |\n| GPS reverse geocoding | ✓ | ✗ | ✗ |\n| Privacy risk detection (ID/passport/bank cards) | ✓ | ✗ | ✗ |\n| iCloud placeholder handling | ✓ | ✗ | ✗ |\n| Video deduplication | ✓ | ~ | ✗ |\n| Live Photo protection | ✓ | ~ | ✗ |\n| Google Takeout import | ✓ | ✗ | ✗ |\n| Quality assessment (7-dimension scoring) | ✓ | ~ | ✗ |\n| macOS Trash recovery | ✓ | ~ | ✗ |\n| Free \u0026 open source | ✓ | ✗ | ~ |\n\n## Table of Contents\n\n- [How SnapTidy Compares](#how-snaptidy-compares)\n- [Why SnapTidy?](#why-snaptidy)\n- [What's New](#whats-new-in-v314)\n- [Key Features](#key-features)\n- [Installation](#installation)\n- [How It Works](#how-it-works)\n- [Safety Guarantees](#safety-guarantees)\n- [Quick Start](#quick-start)\n- [Smart Priority Rules](#smart-priority-rules)\n- [Auto-Categorization](#auto-categorization-15-languages)\n- [Storage \u0026 Performance](#storage--performance)\n- [Supported Formats](#supported-formats)\n- [Scripts Reference](#scripts-reference)\n- [Requirements](#requirements)\n- [Platform Compatibility](#platform-compatibility)\n- [Contributing](#contributing)\n- [Star History](#star-history)\n- [License](#license)\n\n## Why SnapTidy?\n\nYour photo library grows fast — iPhone shots, iCloud exports, Android transfers, WeChat saves, old backups, and screenshots pile up over time. Existing tools like [Sorty](https://github.com/nicoschmdt/sorty), [Tidy](https://github.com/nicoschmdt/tidy), and [Hazelnut](https://github.com/josephearl/hazelnut) are standalone apps you install and configure. **SnapTidy takes a different approach**: it's an AI assistant skill. You describe what you want in natural language, and it handles the rest.\n\n**iPhone users**: You don't need iCloud sync to organize your photos. Connect your iPhone via USB and SnapTidy can scan the Photos.app library directly, or use Finder to sync photos to a local folder first. Tools like [pymobiledevice3](https://github.com/doronz88/pymobiledevice3) also allow direct USB access to your iPhone's DCIM without iCloud.\n\nThe key difference? **Safety first, zero risk.** SnapTidy never deletes anything. It scans read-only, produces a human-readable plan, and only moves files after you explicitly approve — optionally to macOS Trash (recoverable via Finder).\n\n## What's New in v3.14\n\n| Feature | Description |\n|---------|-------------|\n| 🔍 **Bad Extension Detection** | `detect_bad_extensions.py` — detect files whose magic bytes don't match their extension (e.g., JPEG content with `.png`); 20+ format signatures; `--parallel`, `--incremental`, `--report` |\n| 📊 **7-Dimension Quality Scoring** | `assess_quality.py` enhanced from 3 to 7 dimensions: sharpness, exposure, contrast, resolution, format quality, file size efficiency, EXIF completeness; weighted composite score for smarter dedup decisions |\n| 📝 **SKILL.md Simplified** | Reduced from 436 → 91 lines; detailed feature tables moved to `references/features.md`; description shortened to 3 sentences |\n| 🔧 **CI Workflow** | `.github/workflows/ci.yml` — automated syntax checking (42 scripts) + integration tests on every PR/push |\n\n## What's New in v3.13\n\n| Feature | Description |\n|---------|-------------|\n| 🔄 **Batch EXIF Rotation Fix** | `rotate_photos.py` — detect images with incorrect EXIF Orientation, physically rotate pixels to correct direction, reset Orientation to 1; `--dry-run`, `--orientation N` filter, directory scan mode |\n| 🖼️ **Format Conversion** | `convert_format.py` — JPEG/HEIC/PNG → WEBP/AVIF; preserves EXIF GPS/date/camera metadata; 30-50% space savings; `--dry-run` with savings estimate, `--quality N`, `--lossless`, `--keep-originals` |\n| 📍 **GPS Neighbor Inference** | `fix_gps.py` — infer missing GPS from temporally adjacent photos (±10 min window); uses closest reference or averages multiple; `--write-exif`, `--dry-run` |\n| 🎬 **Animated Image Detection** | `is_animated_image()` — detects GIF/animated WebP/APNG; new `is_animated` DB column; scan reports animated count |\n| 🛡️ **Decompression Bomb Protection** | `Image.MAX_IMAGE_PIXELS` set to 60MP — prevents OOM from malicious oversized images |\n| 📱 **AVIF Format Support** | Full AVIF decode support (native Pillow ≥11 or `pillow-avif-plugin`); new `AVIF_EXTS` in constants; scan warns about unconverted AVIF files |\n\n## What's New in v3.12\n\n| Feature | Description |\n|---------|-------------|\n| ☁️ **iCloud Optimization Handling** | Three modes: `--warn-icloud` (default, scan but mark), `--skip-icloud` (skip placeholders), `--download-icloud` (trigger `brctl download` then scan); detects thumbnails via `.icloud` companion files, xattr, and size heuristics |\n| 🔍 **iCloud Check Script** | `check_icloud.py` — standalone tool to scan for iCloud-only files, report count/size/estimates, batch download with progress, verify all files local before downstream processing |\n| 🧹 **Downstream iCloud Filtering** | `find_exact_duplicates.py --exclude-icloud` and `find_similar_photos.py --exclude-icloud` — skip unreliable iCloud placeholder hashes/pHashes in dedup |\n| 📊 **Enhanced Library Health** | `library_stats.py` now shows detailed iCloud status: placeholder count, downloaded count, failed downloads — in terminal and HTML reports |\n| 📦 **Shared iCloud Module** | `icloud_utils.py` — consolidated `check_icloud_status()`, `download_icloud_file()`, `is_likely_thumbnail()`, `batch_download()` into a single reusable module |\n\n\u003cdetails\u003e\n\u003csummary\u003eOlder versions (v3.8 – v3.11)\u003c/summary\u003e\n\n### v3.11\n\n| Feature | Description |\n|---------|-------------|\n| 🏗️ **Unified Extension Definitions** | All format sets consolidated in `constants.py`; added AVIF, WebM, MTS, ORF, RW2, etc.; dot-prefixed variants for direct suffix comparison |\n| ⚡ **Parallel Scanning** | `scan_photos.py --parallel 4` — 2.9x faster; `assess_quality.py --parallel 4` — thread-pool quality assessment |\n| 🔄 **Incremental Scanning** | `scan_photos.py --incremental` — skip unchanged files; second run 35x faster (0.1s vs 3.4s) |\n| 🚀 **pHash Performance** | Prefix-index optimization replaces O(n²) pairwise comparison; scales to 50K+ photo libraries |\n| 🗜️ **Photo Compression** | `compress_photos.py` — Smart JPEG quality by resolution tier; PNG→JPEG conversion; dry-run preview; backup safety |\n| 📅 **Timeline Gap Detection** | `timeline_gaps.py` — Find abnormal date gaps indicating missing photos; adaptive threshold; severity classification |\n\n### v3.10\n\n| Feature | Description |\n|---------|-------------|\n| 💥 **Corrupted File Detection** | `detect_corrupted.py` — Find broken/truncated images and unplayable videos; layered Pillow verify+load, ffmpeg probe; parallel processing |\n| 📅 **Photo Date Correction** | `fix_dates.py` — Fix missing EXIF dates from filename patterns (15+ patterns), neighbor photos, or file mtime; supports `--dry-run`, `--write-exif` |\n| 🔄 **Backup Verification** | `verify_backup.py` — Verify backup completeness; quick (filename+size) or full (SHA-256, catches renames); coverage % report |\n| 📂 **Duplicate Folder Detection** | `find_duplicate_folders.py` — Find folders that are complete or near-complete duplicates; Jaccard similarity; near-duplicate grouping |\n| 💡 **Space What-If** | `library_stats.py --what-if` — \"How much space would I save if I delete all screenshots/duplicates/RAW?\" |\n| 📋 **Event Album Creation** | `organize_photos.py --create-event-albums` — Auto-create Photos.app albums from event clustering results |\n\n### v3.9\n\n| Feature | Description |\n|---------|-------------|\n| 🎯 **Quality Assessment** | `assess_quality.py` — Blur/brightness/contrast/quality score (0-100), integrated with dedup strategy \u0026 review page |\n| 🎵 **Live Photo Detection** | `detect_live_photos.py` — Identify paired HEIC+MOV, keep Live Photos together during dedup |\n| 📷 **Orphan RAW Cleanup** | `find_orphan_raw.py` — Find RAW files without JPEG companion (or vice versa) |\n| 📅 **Timeline Viewer** | `generate_timeline.py` — Interactive HTML timeline, zoom by year/month/day, category filters |\n| 🔄 **Library Compare** | `compare_libraries.py` — Photos.app vs file-system, find unique \u0026 shared photos by SHA-256 |\n| 📥 **Google Takeout Import** | `import_google_takeout.py` — Import Google Photos export, merge JSON metadata to EXIF |\n| 🗺️ **GPX Geotagging** | `gpx_geotag.py` — Assign GPS from GPX track files to photos without coordinates |\n| 📊 **Event Clustering** | `cluster_events.py` — Auto-group photos into events by time + location |\n| 🎬 **Video Dedup** | `find_similar_videos.py` — Frame sampling + pHash for duplicate/similar video detection |\n| ✏️ **Smart Rename** | `rename_photos.py` — Rename by EXIF date/camera/location: `2025-06-15_Beijing_iPhone15_001.jpg` |\n\n### v3.8\n\n| Feature | Description |\n|---------|-------------|\n| 📍 **Reverse Geocoding** | GPS → place names (city/region/country) via CoreLocation (offline), Locationator, or Nominatim; persistent JSON cache |\n| ✏️ **EXIF Editing** | Strip GPS, set dates, write tags — `edit_exif.py` with backup/restore + `--dry-run` safety |\n| 🌍 **By-Location Organize** | `--mode by-location` organizes photos into `Country/Region/City/` folder structure |\n| 📊 **Location Stats** | `library_stats.py` now shows top cities by photo count in terminal \u0026 HTML reports |\n| 📋 **Interactive Review** | `generate_review.py` — HTML review page with smart strategy rules (metadata/oldest/newest/resolution/preferred album), album display, favorites protection |\n| 🔍 **Privacy Risk Detection** | `detect_privacy_risks.py` — find sensitive documents (ID cards, bank cards, passports, passwords) via filename/folder/category/dimension heuristics |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003ev3.7\u003c/summary\u003e\n\n| Feature | Description |\n|---------|-------------|\n| 📊 **Library Health \u0026 Insights** | New read-only `library_stats.py` (and `--mode stats`) — totals, category/format/year breakdowns, health flags (screenshots, no-EXIF, GPS, iCloud-only, possibly-blurry, favorites), top space consumers. Terminal / JSON / HTML output |\n| 🧩 **Shared Modules Refactor** | Extracted `photo_metadata.py`, `constants.py`, `applescript_utils.py` — eliminated ~600 lines of duplicated EXIF/hash/format code (single source of truth) |\n| 🎛️ **Standardized CLI** | Unified flags across all scripts — `--source` / `--index` (`-i`) / `--output` (`-o`), with old `--input`/`--library` names kept as backward-compatible aliases |\n| 🔁 **Before/After Diff Report** | `--mode photos-album` HTML report now shows new/changed/unchanged albums with photo-count deltas (works with `--dry-run`) |\n| 🐛 **Critical Bug Fixes** | Album separator contract, AppleScript injection in trash, NameError in share workflow, emoji drift between organizer \u0026 report |\n\n\u003cdetails\u003e\n\u003csummary\u003ev3.4\u003c/summary\u003e\n\n| Feature | Description |\n|---------|-------------|\n| 🧠 **Apple Quality Vector Detection** | Zero-dependency similarity detection using Apple's pre-computed 17-dim ML feature vectors from `ZCOMPUTEDASSETATTRIBUTES` |\n| 📦 **Optional Dependencies** | Pillow, piexif, imagehash are now optional — core features work with just Python stdlib |\n| 👥 **Semi-Automated Shared Albums** | `--share-to-album` tags \u0026 selects photos in Photos.app, you just drag to shared album (1 step) |\n| 📚 **Lean SKILL.md** | SKILL.md reduced to ≤65 lines, details in `references/` directory (detection, import, performance, priority-rules, troubleshooting) |\n| 🔧 **Union-Find Grouping** | Apple QL detection uses union-find algorithm for proper transitive similarity grouping |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003ev3.3\u003c/summary\u003e\n\n| Feature | Description |\n|---------|-------------|\n| 📱 **Import to Photos.app** | Import from external drives/Android with automatic dedup |\n| 👥 **Shared Album Reading** | Read shared album info from Photos.sqlite |\n| ☁️ **iCloud Sync Awareness** | Detect iCloud-only files and download status |\n| 🔄 **Checkpoint \u0026 Resume** | Import workflow supports checkpoint resume on interruption |\n| 💾 **Zero Data Loss** | Streaming SQLite writes — commit each entry immediately |\n\n\u003c/details\u003e\n\n## Key Features\n\n- 🎯 **SHA-256 Exact Dedup** — Find byte-perfect duplicate files across your entire library\n- 👁️ **Perceptual Hash Similarity** — Detect visually identical images using pHash, with fuzzy Hamming distance threshold\n- 🧠 **Apple Quality Vector Detection** — Zero-dependency similarity via Apple's pre-computed 17-dim ML vectors (`--detect-apple-ql`)\n- 🔀 **Cross-Format Dedup** — HEIC + JPEG of the same photo\n- 📐 **Scaled Dedup** — Same photo at different resolutions\n- 📸 **Burst Detection** — Group burst photos via SubSecTime\n- 📋 **Rich Metadata Index** — Extract file size, EXIF dates, GPS, camera info, dimensions, category, hashes, **place names (city/region/country)** into SQLite or CSV\n- 📍 **Reverse Geocoding** — Convert GPS coordinates to place names (CoreLocation/Nominatim) with persistent cache\n- ✏️ **EXIF Editing** — Strip GPS, set dates, write tags with backup/restore safety\n- 🌍 **By-Location Organize** — Organize photos into `Country/Region/City/` folder structure\n- 📊 **Library Health \u0026 Insights** — Read-only stats report: category/format/year/**location** breakdowns, health flags, top space consumers (terminal / JSON / HTML)\n- 🔍 **Privacy Risk Detection** — Find sensitive documents (ID cards, bank cards, passports, passwords, medical records) via filename/folder/category/dimension heuristics\n- 📋 **Interactive Review** — HTML review page with smart strategy rules (metadata/oldest/newest/resolution/preferred album/quality), album display, favorites protection\n- 🎯 **Quality Assessment** — 7-dimension scoring (sharpness, exposure, contrast, resolution, format, file size, EXIF completeness), integrated with dedup \u0026 review\n- 🎵 **Live Photo Detection** — Keep Live Photo pairs together during dedup\n- 📷 **Orphan RAW Cleanup** — Find RAW files without JPEG companion\n- 📅 **Timeline Viewer** — Interactive HTML timeline with zoom and category filters\n- 🔄 **Library Compare** — Photos.app vs file-system, find unique \u0026 shared by SHA-256\n- 📥 **Google Takeout Import** — Import Google Photos export with metadata merge\n- 🗺️ **GPX Geotagging** — Assign GPS from GPX track files\n- 📊 **Event Clustering** — Auto-group photos by time + location\n- 🎬 **Video Dedup** — Frame sampling + pHash for video duplicates\n- ✏️ **Smart Rename** — Rename by EXIF metadata with configurable templates\n- 💥 **Corrupted Detection** — Find broken/truncated images and unplayable videos\n- 🔄 **EXIF Rotation Fix** — Batch-rotate photos to correct orientation based on EXIF tag\n- 🖼️ **Format Conversion** — Convert JPEG/HEIC to WEBP/AVIF with 30-50% space savings\n- 📍 **GPS Inference** — Infer missing GPS from temporally adjacent photos\n- 🎬 **Animated Detection** — Detect GIF/animated WebP/APNG for smarter dedup\n- 🛡️ **Bomb Protection** — Decompression bomb guard (60MP limit)\n- ☁️ **iCloud Optimization Handling** — Detect, skip, or download iCloud placeholder thumbnails before scanning; standalone `check_icloud.py` for batch pre-download with progress\n- 📅 **Date Correction** — Fix missing EXIF dates from filename patterns, neighbors, or file mtime\n- 🔄 **Backup Verification** — Verify backup completeness (quick or SHA-256 full mode)\n- 📂 **Duplicate Folder Detection** — Find folders that are complete or near-complete duplicates\n- 💡 **Space What-If** — Calculate space savings by removing specific categories\n- 🛡️ **Safety-First Design** — Read-only scanning, move-only operations, Trash mode with Finder recovery, CSV-based audit trail\n- 💾 **Zero Data Loss** — Streaming SQLite writes with per-entry commit\n- 💬 **Conversation-Driven** — Interact through your AI assistant; no GUI or config files needed\n- ⚡ **Zero Config** — Point at a directory and go\n- 🔌 **Multi-Platform** — Works with Claude Code, Cursor, Windsurf, WorkBuddy, OpenClaw, and more\n- 🗄️ **Scalable** — SQLite backend handles 100k+ photos\n- 📦 **Zero-Install Core** — All optional deps gracefully degrade; core features (SHA-256, Apple QL, metadata) work with just Python stdlib\n\n## Installation\n\n### Option 1: One-Prompt Install (Recommended)\n\nJust tell your AI assistant:\n\n\u003e Install this skill: https://github.com/chicogong/snaptidy\n\n### Option 2: CLI Install\n\n```bash\n# Works with 45+ AI platforms\nnpx skills add chicogong/snaptidy\n\n# Or via ClawHub\nclawhub install snaptidy\n```\n\n### Option 3: Manual Install\n\n\u003cdetails\u003e\n\u003csummary\u003eClaude Code\u003c/summary\u003e\n\n```bash\ngit clone https://github.com/chicogong/snaptidy.git ~/.claude/skills/snaptidy\ncd ~/.claude/skills/snaptidy \u0026\u0026 pip install -r requirements.txt\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eCursor\u003c/summary\u003e\n\n```bash\ngit clone https://github.com/chicogong/snaptidy.git\ncp -r snaptidy/.cursor/rules/snaptidy.mdc .cursor/rules/\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eWorkBuddy\u003c/summary\u003e\n\n```bash\ngit clone https://github.com/chicogong/snaptidy.git ~/.workbuddy/skills/snaptidy\ncd ~/.workbuddy/skills/snaptidy \u0026\u0026 pip install -r requirements.txt\n```\n\u003c/details\u003e\n\n## How It Works\n\n![SnapTidy Pipeline](assets/pipeline.svg)\n\n1. **Scan** — Walk through your photo/video directory, extract metadata (size, SHA-256, EXIF date, GPS, camera info, dimensions, perceptual hash, auto-category, folder tag), and write to SQLite (recommended) or CSV\n2. **Find Duplicates** — Group files by exact hash (SHA-256) and perceptual hash (pHash), with optional fuzzy threshold\n3. **Review** — Interactive HTML page to browse duplicates side-by-side, apply smart strategy rules, mark keep/remove\n4. **Generate Plan** — Smart multi-factor scoring decides which duplicate to keep. Supports configurable strategies and folder preferences\n5. **Apply** — Open the CSV plan, verify everything looks right, then apply. Choose between move-to-folder or macOS Trash (recoverable)\n6. **Undo** — Reverse the most recent normal folder move within 30 days; Trash uses its native recovery\n\n## Safety Guarantees\n\n| Guarantee | How |\n|-----------|-----|\n| No automatic deletion | All scripts are read-only by default; `apply_move_plan.py` only moves files |\n| macOS Trash mode | Use `--mode trash` to move to Trash — recoverable via Finder → Put Back |\n| Photos.app trash mode | Recover from Photos.app → Recently Deleted while its retention window applies |\n| Human review required | Move plans are CSV files you can inspect in any spreadsheet app |\n| Full audit trail | Every move is logged to `move_log.csv` with source, destination, status, and reason |\n| Zero data loss | Streaming SQLite writes with per-entry commit — crash loses at most one entry |\n| Skip existing files | If a destination file already exists, the move is skipped automatically |\n| Photos Library protection | `.photoslibrary` and `.photolibrary` directories are never entered |\n| Backup-aware | Directories named `Original_Backup`, `.trashes`, etc. are automatically skipped |\n| Smart priorities | Multi-factor scoring ensures the best-quality photo is always kept |\n\n## Quick Start\n\n### Prerequisites\n\n- **macOS** (tested on 13+)\n- **Python 3.9+**\n- **Full Disk Access** enabled for your terminal (System Settings → Privacy \u0026 Security → Full Disk Access)\n\n### Usage\n\nTell your AI assistant what you want:\n\n\u003e *\"Scan my photo library at /Volumes/Photos and find duplicates\"*\n\nOr run the scripts directly:\n\n```bash\n# Step 1: Scan (SQLite recommended for large libraries, geocoding enabled by default)\npython3 scripts/scan_photos.py --source /path/to/your/photos --output ./photo_index.db\n\n# Step 1b: Scan without geocoding (faster, no place names)\npython3 scripts/scan_photos.py --source /path/to/your/photos --output ./photo_index.db --no-geocode\n\n# Step 1c: Quick scan (zero-install, no deps needed)\npython3 scripts/quick_scan.py --source /path/to/your/photos --output ./photo_index.db --dedup\n\n# Step 1d (Optional): Library health \u0026 insights (read-only)\npython3 scripts/library_stats.py --index ./photo_index.db\npython3 scripts/library_stats.py -i ./photo_index.db --report ./health.html\n\n# Step 2: Find exact duplicates\npython3 scripts/find_exact_duplicates.py --index ./photo_index.db --output ./duplicates_exact.csv\npython3 scripts/find_exact_duplicates.py --index ./photo_index.db --output ./dups.txt --format human\n\n# Step 3 (Optional): Find perceptually similar images\npython3 scripts/find_similar_photos.py --index ./photo_index.db --output ./duplicates_similar.csv\npython3 scripts/find_similar_photos.py --index ./photo_index.db --output ./similar.csv --detect-all\n\n# Step 3b (Optional): Find similar photos using Apple's zero-dependency ML vectors\npython3 scripts/find_similar_photos.py --index ./photo_index.db --output ./similar_apple.csv --detect-apple-ql\npython3 scripts/find_similar_photos.py --index ./photo_index.db --output ./similar_apple.csv --detect-apple-ql --apple-ql-threshold 0.95\n\n# Step 4: Generate a smart move plan\npython3 scripts/generate_move_plan.py \\\n    --duplicates ./duplicates_exact.csv \\\n    --index ./photo_index.db \\\n    --plan ./move_plan.csv \\\n    --target-root /path/to/your/photos \\\n    --prefer-folder \"DCIM\" --strategy quality\n\n# Step 5: Preview with HTML thumbnails (optional but recommended)\n# Use duplicates_exact.csv from Step 2 or duplicates_similar.csv from Step 3\npython3 scripts/generate_preview.py \\\n    --duplicates ./duplicates_similar.csv \\\n    --index ./photo_index.db \\\n    --output ./preview.html\n\n# Step 6: Review move_plan.csv, then apply a normal reversible move\npython3 scripts/apply_move_plan.py --plan ./move_plan.csv --mode move\n\n# Step 7: Undo the normal move if needed (30-day record)\npython3 scripts/apply_move_plan.py --plan ./move_plan.csv --undo\n\n# Alternative after separate confirmation: move to macOS Trash\n# Recover with Finder → Put Back; CLI --undo does not apply to Trash.\npython3 scripts/apply_move_plan.py --plan ./move_plan.csv --mode trash\n```\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://realtime-ai.chat/snaptidy/screenshots/preview-duplicates.png\" alt=\"SnapTidy Duplicate Preview\" width=\"700\"\u003e\n  \u003cem\u003eHTML preview with thumbnails — review before acting\u003c/em\u003e\n\u003c/p\u003e\n\n### Import to Photos.app\n\n```bash\n# Dry-run: preview what would be imported\npython3 scripts/import_to_photos.py --source /Volumes/External/Photos --dry-run\n\n# Import all unique photos (duplicates skipped automatically)\npython3 scripts/import_to_photos.py --source /Volumes/External/Photos --album \"Vacation 2025\"\n\n# Import from Android DCIM\npython3 scripts/import_to_photos.py --source /Volumes/Android/DCIM --album \"Android Import\"\n\n# Semi-automated shared album workflow (1 manual drag step)\npython3 scripts/import_to_photos.py --source /Volumes/External/Photos \\\n    --album \"Vacation 2025\" \\\n    --share-to-album \"Vacation 2025\"\n\n# List shared albums (read-only)\npython3 scripts/import_to_photos.py --show-shared-albums\n```\n\n### Photo Rotation, Conversion \u0026 GPS Inference\n\n```bash\n# Fix EXIF orientation (dry-run first, then apply)\npython3 scripts/rotate_photos.py --index ./photo_index.db --dry-run\npython3 scripts/rotate_photos.py --index ./photo_index.db\n\n# Convert JPEG/HEIC to WEBP (30-50% space savings)\npython3 scripts/convert_format.py --index ./photo_index.db --to webp --dry-run\npython3 scripts/convert_format.py --index ./photo_index.db --to webp --quality 85\n\n# Convert only large files to AVIF\npython3 scripts/convert_format.py --source /path/to/photos --to avif --min-size 500 --dry-run\n\n# Infer missing GPS from neighboring photos\npython3 scripts/fix_gps.py --index ./photo_index.db --dry-run\npython3 scripts/fix_gps.py --index ./photo_index.db --write-exif\n```\n\n### One-Command Interactive Workflow\n\n```bash\n# Interactive — asks preferences step by step\npython3 scripts/organize_photos.py --source ~/Pictures/Export --interactive\n\n# Non-interactive with dry-run\npython3 scripts/organize_photos.py \\\n    --source ~/Pictures/Export --dedup-method all \\\n    --strategy quality --trash-mode trash --dry-run\n\n# Organize by date into YYYY/MM folders\npython3 scripts/organize_photos.py --source ~/Pictures/Export --mode by-date --dry-run\n\n# Organize by category (01_Photos, 02_Screenshots, 03_WeChat, etc.)\npython3 scripts/organize_photos.py --source ~/Pictures/Export --mode by-category --dry-run\n\n# Organize by location (Country/Region/City/filename)\npython3 scripts/organize_photos.py --source ~/Pictures/Export --mode by-location --dry-run\n\n# Detect connected Android devices and external drives\npython3 scripts/organize_photos.py --source /any --detect-sources\n```\n\n### Reverse Geocoding\n\n```bash\n# Look up a single GPS coordinate\npython3 scripts/reverse_geocode.py --lat 39.9042 --lon 116.4074\n\n# Specify backend and language\npython3 scripts/reverse_geocode.py --lat 37.7749 --lon -122.4194 --backend nominatim --lang en\n\n# Set custom cache directory\npython3 scripts/reverse_geocode.py --lat 31.2304 --lon 121.4737 --cache-dir ./geocache\n```\n\n### Interactive Review\n\nReview duplicates before deleting — **never acts on files directly**, only records your decisions:\n\n```bash\n# Generate interactive review page\npython3 scripts/generate_review.py \\\n    --index ./photo_index.db \\\n    --duplicates ./duplicates_exact.csv \\\n    --similar ./duplicates_similar.csv \\\n    --output ./review.html\n\n# Open review.html in browser, mark keep/remove, export decision CSV\n```\n\n**Smart strategy rules** (apply to all groups at once):\n| Strategy | Keeps | Best for |\n|----------|-------|----------|\n| Most metadata | Highest EXIF/camera/GPS/date completeness | Keep the most informative version |\n| Oldest | Earliest capture date | Keep the original |\n| Newest | Latest modification date | Keep the final edit |\n| Highest resolution | Largest pixel dimensions | Keep the sharpest version |\n| Preferred album | Photos from specified album | Keep photos in your favorite album |\n\n⭐ Photos marked as favorites are never auto-marked for deletion.\n\n### Privacy Risk Detection\n\nFind sensitive documents that shouldn't be in your photo library:\n\n```bash\n# Scan for privacy risks (auto-detect format from extension)\npython3 scripts/detect_privacy_risks.py --index ./photo_index.db --output ./privacy_report.txt\n\n# JSON format for scripting\npython3 scripts/detect_privacy_risks.py --index ./photo_index.db --output ./privacy_report.json\n\n# CSV format for spreadsheet review\npython3 scripts/detect_privacy_risks.py --index ./photo_index.db --output ./privacy_report.csv\n\n# Only show high-risk and above\npython3 scripts/detect_privacy_risks.py --index ./photo_index.db --output ./report.txt --min-risk high\n```\n\n**Detection methods**: filename patterns (ID cards, passports, bank cards, passwords), folder path analysis, category+keyword matching (financial app screenshots), dimension heuristics (card-shaped images).\n\n### Quality Assessment\n\nAssess 7 quality dimensions for smarter dedup decisions:\n\n```bash\n# Assess quality and write scores to DB\npython3 scripts/assess_quality.py --index ./photo_index.db\n\n# Export quality report\npython3 scripts/assess_quality.py --index ./photo_index.db --report quality_report.csv\n\n# Incremental (only un-scored photos)\npython3 scripts/assess_quality.py --index ./photo_index.db --incremental\n```\n\nScores are automatically used in dedup: `generate_move_plan.py --strategy quality` considers blur penalty and quality score bonus. The review page shows quality badges (Q0-100) and adds \"Keep best quality\" strategy.\n\n### Live Photo Detection\n\n```bash\n# Detect Live Photo pairs\npython3 scripts/detect_live_photos.py --index ./photo_index.db\n\n# Export report\npython3 scripts/detect_live_photos.py --index ./photo_index.db --report live_photos.json\n```\n\n### Timeline Viewer\n\n```bash\n# Generate interactive timeline\npython3 scripts/generate_timeline.py --index ./photo_index.db --output ./timeline.html\n\n# Limit thumbnails for large libraries\npython3 scripts/generate_timeline.py --index ./photo_index.db --output ./timeline.html --max-thumbs 2000\n\n# Filter by year range\npython3 scripts/generate_timeline.py --index ./photo_index.db --output ./timeline.html --from-year 2024\n```\n\n### Orphan RAW Cleanup\n\n```bash\n# Find RAW files without JPEG companion\npython3 scripts/find_orphan_raw.py --index ./photo_index.db --output ./orphan_raw.csv\n\n# Find both orphan RAW and orphan JPEG\npython3 scripts/find_orphan_raw.py --index ./photo_index.db --output ./orphan_report.csv --both\n```\n\n### Library Compare\n\n```bash\n# Compare Photos.app vs file-system\npython3 scripts/compare_libraries.py \\\n    --library ~/Pictures/Photos\\ Library.photoslibrary \\\n    --index ./photo_index.db \\\n    --output comparison.json\n\n# Only show unique items\npython3 scripts/compare_libraries.py \\\n    --library ~/Pictures/Photos\\ Library.photoslibrary \\\n    --index ./photo_index.db \\\n    --output comparison.csv --unique-only\n```\n\n### Google Takeout Import\n\n```bash\n# Scan and index Google Takeout\npython3 scripts/import_google_takeout.py \\\n    --source ~/Downloads/takeout-20250615 \\\n    --output ./takeout_index.db\n\n# Also write metadata to EXIF\npython3 scripts/import_google_takeout.py \\\n    --source ~/Downloads/takeout-20250615 \\\n    --output ./takeout_index.db --write-exif\n```\n\n### GPX Geotagging\n\n```bash\n# Geotag photos from GPX track\npython3 scripts/gpx_geotag.py --index ./photo_index.db --gpx track.gpx\n\n# Preview only (dry-run)\npython3 scripts/gpx_geotag.py --index ./photo_index.db --gpx track.gpx --dry-run\n\n# Also write GPS to EXIF, with timezone offset\npython3 scripts/gpx_geotag.py --index ./photo_index.db --gpx track.gpx --write-exif --timezone-offset +8\n```\n\n### Event Clustering\n\n```bash\n# Cluster photos into events (4-hour gap)\npython3 scripts/cluster_events.py --index ./photo_index.db --output events.json\n\n# Custom gap and location-aware\npython3 scripts/cluster_events.py --index ./photo_index.db --output events.csv --gap-hours 2 --use-location\n```\n\n### Video Dedup\n\n```bash\n# Find similar videos (requires ffmpeg)\npython3 scripts/find_similar_videos.py --index ./photo_index.db --output similar_videos.csv\n```\n\n### Smart Rename\n\n```bash\n# Preview rename (dry-run)\npython3 scripts/rename_photos.py --index ./photo_index.db --template \"{date}_{camera}_{seq}\"\n\n# Execute rename\npython3 scripts/rename_photos.py --index ./photo_index.db --template \"{date}_{camera}_{seq}\" --execute\n\n# Rename with location\npython3 scripts/rename_photos.py --index ./photo_index.db --template \"{date}_{city}_{seq}\" --execute\n```\n\n### Corrupted File Detection\n\n```bash\n# Check for corrupted images and videos\npython3 scripts/detect_corrupted.py --index ./photo_index.db\n\n# With CSV report and parallel processing\npython3 scripts/detect_corrupted.py --index ./photo_index.db --report corrupted.csv --parallel 8\n\n# Incremental (only check files not yet verified)\npython3 scripts/detect_corrupted.py --index ./photo_index.db --incremental\n```\n\n### Fix Missing Dates\n\n```bash\n# Fix dates from all sources (filename, neighbors, file mtime)\npython3 scripts/fix_dates.py --index ./photo_index.db --dry-run\n\n# Actually fix dates and write to EXIF\npython3 scripts/fix_dates.py --index ./photo_index.db --write-exif --report fixed.csv\n\n# Only use filename extraction\npython3 scripts/fix_dates.py --index ./photo_index.db --strategy filename-only\n```\n\n### Backup Verification\n\n```bash\n# Quick check (filename + size)\npython3 scripts/verify_backup.py --source ~/Photos --backup /Volumes/Backup/Photos\n\n# Full SHA-256 check (catches renamed files)\npython3 scripts/verify_backup.py --source ~/Photos --backup /Volumes/Backup/Photos --full\n\n# Using existing index DB\npython3 scripts/verify_backup.py --index ./photo_index.db --backup /Volumes/Backup/Photos --full --report report.csv\n```\n\n### Duplicate Folder Detection\n\n```bash\n# Find folders with ≥50% similar content\npython3 scripts/find_duplicate_folders.py --index ./photo_index.db\n\n# Scan filesystem directly (slower, no index needed)\npython3 scripts/find_duplicate_folders.py --source ~/Photos --threshold 0.7 --report dup_folders.csv\n```\n\n### Space What-If Analysis\n\n```bash\n# \"How much space would I save if I delete all screenshots?\"\npython3 scripts/library_stats.py --index ./photo_index.db --what-if\n\n# With HTML report\npython3 scripts/library_stats.py --index ./photo_index.db --what-if --report savings.html\n```\n\n### iCloud Optimization Handling\n\nWhen macOS \"Optimize Storage\" is enabled, iCloud offloads original photos to the cloud and keeps only small thumbnails locally (2-50 KB). These thumbnails produce unreliable SHA-256 hashes and pHashes, causing false results in dedup.\n\n```bash\n# Step 1: Check which files are iCloud-only (before scanning)\npython3 scripts/check_icloud.py --source ~/Pictures/Photos --report\n\n# Step 2: Download all iCloud files (with progress + size estimates)\npython3 scripts/check_icloud.py --source ~/Pictures/Photos --download\n\n# Step 2b: Dry-run — see what would be downloaded\npython3 scripts/check_icloud.py --source ~/Pictures/Photos --download --dry-run\n\n# Step 3: Scan with iCloud awareness\npython3 scripts/scan_photos.py -i ~/Pictures/Photos -o index.db                        # warn (default)\npython3 scripts/scan_photos.py -i ~/Pictures/Photos -o index.db --skip-icloud            # skip placeholders\npython3 scripts/scan_photos.py -i ~/Pictures/Photos -o index.db --download-icloud       # download then scan\n\n# Step 4: Dedup with iCloud exclusion (skip unreliable placeholder hashes)\npython3 scripts/find_exact_duplicates.py -i index.db -o dups.csv --exclude-icloud\npython3 scripts/find_similar_photos.py -i index.db -o similar.csv --exclude-icloud\n```\n\n**Detection methods** (all three checked for each file):\n1. `.icloud` companion file exists (iCloud Drive style)\n2. `com.apple.iCloud.syncState` extended attribute\n3. Size heuristic (HEIC \u003c 100 KB or JPEG \u003c 20 KB = likely thumbnail)\n\n### EXIF Editing\n\n```bash\n# Strip GPS data from all photos in the index (dry-run first!)\npython3 scripts/edit_exif.py strip-gps --index ./photo_index.db --dry-run\n\n# Actually strip GPS data\npython3 scripts/edit_exif.py strip-gps --index ./photo_index.db\n\n# Only strip GPS from photos that have GPS data\npython3 scripts/edit_exif.py strip-gps --index ./photo_index.db --only-gps\n\n# Set EXIF date on specific files\npython3 scripts/edit_exif.py set-date --date \"2025-06-15T14:30:00\" --paths photo1.jpg photo2.heic\n\n# Write tags/keywords to specific files\npython3 scripts/edit_exif.py set-tags --tags \"vacation,beach,summer\" --paths photo1.jpg photo2.jpg\n```\n\n## Smart Priority Rules\n\nWhen deciding which duplicate to KEEP, SnapTidy scores files by:\n\n| Factor | Weight | Rationale |\n|--------|--------|-----------|\n| Resolution (pixels) | High (0–100) | Higher res = better quality |\n| File size | Medium (0–50) | Larger = less compressed |\n| EXIF completeness | High (+30) | Has metadata = likely original |\n| Format (RAW +20, HEIC +10) | Medium | Better format = better quality |\n| Category (photo +15, screenshot -20, wechat -10) | Medium | Real photos over screenshots |\n| Folder preference | Configurable (+50) | User-specified priority folders |\n| Photos.app favorite | High (+50) | Never move favorited photos |\n\n**Strategies** (`--strategy`): `quality` (default), `oldest`, `newest`, `folder`\n\n## Auto-Categorization (15+ Languages)\n\n| Category | Detected by |\n|----------|------------|\n| photo | Default for camera photos |\n| screenshot | \"screenshot\", \"截图\", \"截屏\", \"スクリーンショット\", \"스크린샷\", \"скриншот\" |\n| wechat | \"mmexport\", \"wx_camera_\", \"microMsg\", \"WeiXin\" |\n| burst | \"_HDR\", \"_burst\", \"连拍\", \"連拍\", \"버스트\" |\n| video | Video file extensions |\n\n## Storage \u0026 Performance\n\n| Format | Best For | Speed | Context Impact |\n|--------|----------|-------|----------------|\n| **SQLite** (.db) | 100k+ photos | 400x faster queries | Data stays in local DB, no context bloat |\n| **CSV** (.csv) | Small libraries (\u003c10k) | Fine for small sets | CSV content may bloat AI context |\n\n### Benchmarks (MacBook Pro M3 Pro)\n\n| Photos | Scan | Exact | Similar (all) | Plan | Total |\n|--------|------|-------|---------------|------|-------|\n| 1K | 1.3s | 0.06s | 1.2s | 0.1s | ~3s |\n| 10K | 12s | 0.07s | 49s | 0.3s | ~66s |\n| 50K | 58s | 0.13s | ~8min | 0.5s | ~10min |\n\n## Supported Formats\n\n| Type | Extensions |\n|------|-----------|\n| Images | jpg, jpeg, png, bmp, gif, tif, tiff, heic, heif, webp |\n| RAW | dng, cr2, nef, arw |\n| Videos | mov, mp4, m4v, avi, mkv, 3gp, mpg, mpeg, hevc, wmv, flv |\n\n## Scripts Reference\n\n| Script | Purpose | Input | Output |\n|--------|---------|-------|--------|\n| `quick_scan.py` | Zero-install quick scan (stdlib only, SHA-256 + Apple QL) | Photo directory or `.photoslibrary` | `.db` |\n| `scan_photos.py` | Walk directory, extract metadata + GPS + camera + **place names** | Photo/video directory | `.db` or `.csv` |\n| `scan_photos_library.py` | Scan Photos.app library (reads Photos.sqlite) | `.photoslibrary` bundle | `.db` or `.csv` |\n| `find_exact_duplicates.py` | Group byte-identical files by SHA-256 | `.db` or `.csv` index | `duplicates_exact.csv` |\n| `find_similar_photos.py` | Group visually identical images by pHash, Apple QL, scaled, cross-format, burst | `.db` or `.csv` index | `duplicates_similar.csv` |\n| `generate_move_plan.py` | Smart priority scoring, propose which to move | Duplicates CSV + index | `move_plan.csv` |\n| `apply_move_plan.py` | Execute move plan (move or Trash mode) + undo | `move_plan.csv` | `move_log.csv` |\n| `organize_photos.py` | One-command interactive pipeline (by-date/by-category/**by-location**) | Source directory | Full pipeline output |\n| `import_to_photos.py` | Import to Photos.app with dedup | Source directory | Import report JSON |\n| `generate_preview.py` | HTML thumbnail preview | Duplicates CSV + index | `preview.html` |\n| `generate_review.py` | Interactive HTML review with smart strategy rules | `.db` index + duplicates CSVs | `review.html` + decision CSV |\n| `detect_privacy_risks.py` | Find sensitive documents (ID/bank cards, passports, passwords) | `.db` index | `.json` / `.csv` / `.txt` report |\n| `assess_quality.py` | 7-dimension quality scoring (0-100) | `.db` index | DB columns + `.csv` / `.json` report |\n| `detect_live_photos.py` | Identify Live Photo pairs (HEIC+MOV) | `.db` index | `live_photo_group` column |\n| `find_orphan_raw.py` | Find orphan RAW files without JPEG companion | `.db` index | `.csv` / `.json` report |\n| `generate_timeline.py` | Interactive HTML timeline (year/month/day zoom) | `.db` index | `timeline.html` |\n| `compare_libraries.py` | Compare Photos.app vs file-system (SHA-256) | `.db` + `.photoslibrary` | `.json` / `.csv` report |\n| `import_google_takeout.py` | Import Google Photos export + merge JSON metadata | Takeout directory | `.db` index |\n| `gpx_geotag.py` | Assign GPS from GPX track files | `.db` index + `.gpx` | DB columns + EXIF |\n| `cluster_events.py` | Auto-group photos into events by time+location | `.db` index | `.json` / `.csv` report |\n| `find_similar_videos.py` | Video dedup via frame sampling + pHash | `.db` index | `.csv` report |\n| `rename_photos.py` | Smart rename by EXIF date/camera/location | `.db` index | Renamed files + undo record |\n| `detect_corrupted.py` | Find broken/truncated images and unplayable videos | `.db` index | DB columns + `.csv` report |\n| `fix_dates.py` | Fix missing EXIF dates from filename/neighbors/mtime | `.db` index | DB columns + `.csv` report |\n| `verify_backup.py` | Verify backup completeness (quick or SHA-256 full) | Source + backup dirs | `.csv` report + coverage % |\n| `find_duplicate_folders.py` | Find duplicate/similar folders by content hash | `.db` index or directory | `.csv` report |\n| `compress_photos.py` | Smart photo compression (resolution-based quality, PNG→JPEG) | `.db` index | Compressed files + `.csv` report |\n| `timeline_gaps.py` | Detect abnormal date gaps (missing photo periods) | `.db` index | `.csv` report + terminal summary |\n| `check_icloud.py` | Detect \u0026 pre-download iCloud placeholder files | Photo directory | Report + batch download |\n| `generate_album_report.py` | HTML album organization report (before/after diff) | `.db` index + stats | `album_report.html` |\n| `library_stats.py` | Library health \u0026 insights + **space what-if analysis** | `.db` index | terminal / JSON / `health.html` |\n| `reverse_geocode.py` | GPS → place names (city/region/country) | Lat/lon coordinates | Place name text |\n| `edit_exif.py` | EXIF editing: strip GPS, set dates, write tags | Index DB or file paths | Modified files + log |\n| `icloud_utils.py` · `photo_metadata.py` · `constants.py` · `applescript_utils.py` | Shared internal modules (iCloud detection, hashing/EXIF, constants, AppleScript) | — | — |\n| `rotate_photos.py` | Batch-rotate photos to correct EXIF orientation | `.db` index or directory | Rotated files + CSV report |\n| `convert_format.py` | Convert JPEG/HEIC/PNG → WEBP/AVIF (save 30-50% space) | `.db` index or directory | Converted files + CSV report |\n| `fix_gps.py` | Infer missing GPS from temporally adjacent photos | `.db` index | DB update + optional EXIF + CSV |\n\n## Requirements\n\n### Core (zero-install)\n\n| What | How |\n|------|-----|\n| Python 3.9+ | Built-in stdlib: `hashlib`, `sqlite3`, `os`, `argparse`, `json`, `math` |\n| Apple QL Detection | Reads pre-computed vectors from `ZCOMPUTEDASSETATTRIBUTES` — no extra deps |\n| SHA-256 dedup | Uses `hashlib.sha256` from stdlib |\n\n### Optional (enhanced features when installed)\n\n| Package | Purpose | Fallback if missing |\n|---------|---------|---------------------|\n| **Pillow** | Image dimensions, format detection | Dimensions from Photos.app metadata |\n| **piexif** | EXIF dates, GPS, camera info | Date from file mtime/Photos.app |\n| **imagehash** | Perceptual hash (pHash) similarity | Apple QL detection (zero-dep alternative) |\n| **pillow-heif** | HEIC/HEIF full support | HEIC files skipped for pHash |\n| **photoscript** | High-level Photos.app import | osascript fallback (no extra deps) |\n| **pyobjc-framework-Photos** | Low-level Photos.app control | osascript fallback |\n\n**All optional dependencies degrade gracefully** — SnapTidy prints a warning and continues with reduced functionality. No crashes, no hard requirements.\n\n## Platform Compatibility\n\n| Platform | Config File | Install Path |\n|----------|------------|--------------|\n| Claude Code | `CLAUDE.md` | `~/.claude/skills/snaptidy/` |\n| Cursor | `.cursor/rules/snaptidy.mdc` | Project `.cursor/rules/` |\n| Windsurf | `.windsurf/rules/snaptidy.md` | Project `.windsurf/rules/` |\n| WorkBuddy | `SKILL.md` | `~/.workbuddy/skills/snaptidy/` |\n| OpenClaw | `SKILL.md` + `clawhub.yaml` | `~/.openclaw/skills/snaptidy/` |\n| Any AI agent | `AGENTS.md` | Project root |\n\n## Contributing\n\nContributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.\n\nSome areas where help is especially appreciated:\n\n- **Date-based reorganization** — Sort photos into year/month folders based on EXIF dates\n- **Video deduplication** — Key-frame hashing for video files using ffmpeg/opencv\n- **Cross-platform support** — Extend beyond macOS to Linux and Windows\n- **Offline geocoding fallback** — Bundle a lightweight offline reverse-geocode database\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for a history of notable changes.\n\n## License\n\nThis project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details.\n\n## Star History\n\n\u003ca href=\"https://star-history.com/#chicogong/snaptidy\u0026Date\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://star-history.com/embed?username=chicogong\u0026repo=snaptidy\u0026theme=dark\u0026Date\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://star-history.com/embed?username=chicogong\u0026repo=snaptidy\u0026Date\"\u003e\n    \u003cimg alt=\"Star History Chart\" src=\"https://star-history.com/embed?username=chicogong\u0026repo=snaptidy\u0026Date\"\u003e\n  \u003c/picture\u003e\n\u003c/a\u003e\n\n## Acknowledgments\n\nInspired by the macOS automation community and tools like [organize](https://github.com/tfeldmann/organize), [FileLens](https://github.com/priyanshul/get-file-details), [Anthropic Skills](https://github.com/anthropics/skills), and the [Apple CLI](https://github.com/Sankalpcreat/Apple-CLI) ecosystem.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchicogong%2Fsnaptidy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchicogong%2Fsnaptidy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchicogong%2Fsnaptidy/lists"}