{"id":35201583,"url":"https://github.com/richardjlyon/immich-lib","last_synced_at":"2026-01-13T21:40:22.606Z","repository":{"id":330809694,"uuid":"1123893720","full_name":"richardjlyon/immich-lib","owner":"richardjlyon","description":"Intelligent Immich duplicate management - selects highest quality images by dimensions and consolidates metadata before cleanup","archived":false,"fork":false,"pushed_at":"2025-12-29T08:43:27.000Z","size":14463,"stargazers_count":16,"open_issues_count":1,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-12-30T14:19:22.364Z","etag":null,"topics":["duplicates-removal","exif","immich-api","photos"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/richardjlyon.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"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":null,"dco":null,"cla":null}},"created_at":"2025-12-27T21:27:32.000Z","updated_at":"2025-12-29T10:29:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/richardjlyon/immich-lib","commit_stats":null,"previous_names":["richardjlyon/immich-lib"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/richardjlyon/immich-lib","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richardjlyon%2Fimmich-lib","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richardjlyon%2Fimmich-lib/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richardjlyon%2Fimmich-lib/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richardjlyon%2Fimmich-lib/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/richardjlyon","download_url":"https://codeload.github.com/richardjlyon/immich-lib/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richardjlyon%2Fimmich-lib/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28400995,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T14:36:09.778Z","status":"ssl_error","status_checked_at":"2026-01-13T14:35:19.697Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["duplicates-removal","exif","immich-api","photos"],"created_at":"2025-12-29T11:22:27.738Z","updated_at":"2026-01-13T21:40:22.601Z","avatar_url":"https://github.com/richardjlyon.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# immich-dupes\n\n[Changelog](CHANGELOG.md)\n\n\u003e **Warning: External Libraries Not Supported**\n\u003e\n\u003e This tool's metadata consolidation (GPS, timezone transfer) **does not work** with Immich External Libraries (library imports). Immich reads metadata from the source files for external libraries, so API updates don't persist.\n\u003e\n\u003e This tool only works correctly with **uploaded assets** (files uploaded via the Immich app/web/CLI that Immich manages directly).\n\nA Rust CLI tool for intelligent Immich duplicate management. Unlike Immich's built-in de-duplication which favors larger files, this tool selects the highest-quality image by dimensions while preserving metadata through consolidation.\n\n**Features:**\n- Smart duplicate removal with metadata preservation\n- iPhone letterbox detection (4:3 vs 16:9 crop pairs)\n\n## The Problem\n\nImmich's duplicate detection works well, but its resolution logic is naive: keep the largest file, trash the rest. This ignores that:\n\n- Smaller files often have richer EXIF metadata (GPS, timezone, camera info)\n- Photos from different sources have varying metadata completeness\n- Once deleted, metadata is gone forever\n\nWith 2000+ duplicates, manual review isn't feasible. This tool automates smart selection with full backup safety.\n\n## How It Works\n\n1. **Analyze** - Scans Immich duplicates, scores by metadata completeness, outputs reviewable JSON\n2. **Execute** - Downloads backups, consolidates metadata to winners, deletes losers\n3. **Verify** - Validates winners exist and losers are deleted\n4. **Restore** - Re-uploads backed-up files if needed\n\n### Winner Selection\n\nThe tool selects winners by **largest dimensions** (width × height), ensuring you keep the highest quality image. Metadata from losers (GPS, timezone) is consolidated to the winner before deletion.\n\n## Installation\n\n### Homebrew (macOS)\n\n```bash\nbrew install richardjlyon/tap/immich-dupes\n```\n\n### Pre-built binaries\n\nDownload from [Releases](https://github.com/richardjlyon/immich-lib/releases/latest) for Linux, macOS, and Windows.\n\n### From source\n\n```bash\ncargo install --git https://github.com/richardjlyon/immich-lib\n```\n\n## Usage\n\n### Setup\n\nSet your Immich credentials:\n\n```bash\nexport IMMICH_URL=\"https://your-immich-server.com\"\nexport IMMICH_API_KEY=\"your-api-key\"\n```\n\nOr use command-line flags: `-u \u003cURL\u003e -a \u003cAPI_KEY\u003e`\n\n### Analyze Duplicates\n\n```bash\nimmich-dupes analyze -o duplicates.json\n```\n\nThis outputs a JSON file with all duplicate groups, scored assets, and conflict detection. Review the file to spot-check decisions.\n\n### Execute Removal\n\n```bash\nimmich-dupes execute -i duplicates.json -b ./backups\n```\n\nThis will:\n1. Download all loser files to `./backups/`\n2. Consolidate GPS/timezone metadata from losers to winners\n3. Move losers to Immich trash (or permanently delete with `--force`)\n\n**Options:**\n- `--skip-review` - Skip groups with metadata conflicts that need manual review\n- `--yes` - Skip confirmation prompt\n- `--rate-limit \u003cN\u003e` - Max API requests per second (default: 10)\n- `--concurrent \u003cN\u003e` - Max concurrent operations (default: 5)\n\n### Verify Results\n\n```bash\nimmich-dupes verify duplicates.json\n```\n\nChecks that all winners still exist and all losers have been deleted.\n\n### Restore Backups\n\nIf something went wrong:\n\n```bash\nimmich-dupes restore -b ./backups\n```\n\nRe-uploads all backed-up files to Immich.\n\n## Example Workflow\n\n```bash\n# 1. Analyze your duplicates\nimmich-dupes analyze -o analysis.json\n\n# 2. Review the JSON (optional)\ncat analysis.json | jq '.groups | length'  # See group count\ncat analysis.json | jq '.needs_review_count'  # See conflicts\n\n# 3. Execute with backups\nimmich-dupes execute -i analysis.json -b ./backups --skip-review\n\n# 4. Verify the results\nimmich-dupes verify analysis.json\n\n# 5. If needed, restore\nimmich-dupes restore -b ./backups\n```\n\n## iPhone Letterbox Detection\n\niPhones can save photos in both 4:3 (full sensor) and 16:9 (cropped) formats simultaneously. This creates near-duplicate pairs that Immich's CLIP-based detection doesn't catch because they're semantically identical but have different aspect ratios.\n\nThe `letterbox` command finds and removes these pairs:\n\n```bash\n# 1. Analyze for letterbox pairs\nimmich-dupes letterbox analyze -o letterbox.json\n\n# 2. Review the analysis\ncat letterbox.json | jq '.pairs | length'  # Count of pairs found\n\n# 3. Execute removal (backs up 16:9 crops, keeps 4:3 originals)\nimmich-dupes letterbox execute -i letterbox.json -b ./letterbox-backups\n\n# 4. Verify results\nimmich-dupes letterbox verify letterbox.json\n```\n\n### How It Works\n\n1. **Detection** - Matches photos by timestamp + camera make/model + GPS\n2. **Selection** - Always keeps the 4:3 version (more pixels, full scene)\n3. **Backup** - Downloads 16:9 crops before deletion\n4. **Cleanup** - Moves 16:9 crops to trash (or deletes with `--force`)\n\n### Output\n\nThe analysis shows:\n- Pairs found (4:3 + 16:9 matches)\n- Space recoverable (size of 16:9 files to delete)\n- Skipped non-iPhone assets\n- Skipped ambiguous groups (multiple candidates)\n\n## What Gets Consolidated\n\nWhen a loser has metadata the winner lacks, it's transferred:\n\n| Field | Consolidated |\n|-------|--------------|\n| GPS coordinates | Yes |\n| Timezone | Yes |\n| Description | Yes |\n| Camera make/model | No (Immich API limitation) |\n\n## Safety Features\n\n- **Two-stage workflow** - Review JSON before any deletions\n- **Full backups** - Original files downloaded before deletion\n- **Trash by default** - Uses Immich trash, not permanent delete\n- **Conflict detection** - Flags groups with conflicting metadata for review\n- **Verification** - Confirm end state matches expectations\n- **Restore capability** - Re-upload backups if needed\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frichardjlyon%2Fimmich-lib","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frichardjlyon%2Fimmich-lib","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frichardjlyon%2Fimmich-lib/lists"}