From f8035e933807961341ca054c03faf524e605aa83 Mon Sep 17 00:00:00 2001 From: Major Date: Sun, 7 Jun 2026 11:44:44 +0200 Subject: [PATCH 1/5] docs(overview): simplify feature descriptions Rewrite feature list to be more concise and action-oriented, focusing on what users can do rather than implementation details. Improve readability by grouping related capabilities. Change-Type: docs Scope: overview --- README.md | 19 +++++++++---------- docs/index.md | 23 ++++++++++++----------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 3a30ade..87117c5 100644 --- a/README.md +++ b/README.md @@ -61,16 +61,15 @@ Identifies trashed assets and moves their related stack members to trash for con ## Features -- **Automatic Stacking:** Groups similar photos into stacks based on filename, date, and custom criteria -- **Smart Burst Photo Handling:** Automatically detects and properly orders burst photo sequences -- **Duplicate Detection:** Find and list duplicate assets based on filename and timestamp -- **Stack-Aware Trash Management:** Fix incomplete trash operations by moving related stack members to trash -- **Multi-User Support:** Process multiple users sequentially with comma-separated API keys -- **Configurable Grouping:** Custom grouping logic via environment variables and command-line flags -- **Parent/Child Promotion:** Fine-grained control over stack parent selection with intelligent sequence detection and regex-based promotion -- **Safe Operations:** Dry-run mode, stack replacement, and reset with confirmation -- **Comprehensive Logging:** Colorful, structured logs for all operations -- **Tested and Modular:** Table-driven tests and clear separation of concerns +- Groups similar photos into stacks based on filename, date, and custom criteria. +- Detects burst sequences via the `sequence` keyword (Sony's `DSCPDC_0001_BURST`, + Canon's `IMG_0001`, and similar patterns). +- Lists duplicates by filename and capture time. +- Cleans up trash operations: moves stack members of trashed assets to trash too. +- Runs against multiple users in one go (comma-separated API keys). +- Lets you pick the stack parent by extension, filename pattern, regex, or size. +- Dry-run mode, stack replacement, and a confirmation-gated reset. +- Colorized, structured logs at configurable verbosity. ## License diff --git a/docs/index.md b/docs/index.md index d7a354d..254d430 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,19 +1,20 @@ # Immich Stack -Immich Stack is a Go CLI tool and library for automatically grouping ("stacking") similar photos in the [Immich](https://github.com/immich-app/immich) photo management system. It provides configurable, robust, and extensible logic for grouping, sorting, and managing photo stacks via the Immich API. +Immich Stack is a Go CLI (and library) that groups similar photos into stacks +in [Immich](https://github.com/immich-app/immich). Grouping criteria, sort +order, and parent selection are configurable via env vars or CLI flags. ## Features -- **Automatic Stacking:** Groups similar photos into stacks based on filename, date, and custom criteria -- **Smart Burst Photo Handling:** Automatically detects and properly orders burst photo sequences with the flexible `sequence` keyword (e.g., Sony's DSCPDC_0001_BURST, Canon's IMG_0001, etc.) -- **Duplicate Detection:** Find and list duplicate assets based on filename and timestamp -- **Stack-Aware Trash Management:** Fix incomplete trash operations by moving related stack members to trash -- **Multi-User Support:** Process multiple users sequentially with comma-separated API keys -- **Configurable Grouping:** Custom grouping logic via environment variables and command-line flags -- **Parent/Child Promotion:** Fine-grained control over stack parent selection with intelligent sequence detection and the `sequence` keyword -- **Safe Operations:** Dry-run mode, stack replacement, and reset with confirmation -- **Comprehensive Logging:** Colorful, structured logs with configurable levels and formats -- **Tested and Modular:** Table-driven tests and clear separation of concerns +- Groups similar photos into stacks based on filename, date, and custom criteria. +- Detects burst sequences via the `sequence` keyword (Sony's `DSCPDC_0001_BURST`, + Canon's `IMG_0001`, and similar patterns). +- Lists duplicates by filename and capture time. +- Cleans up trash operations: moves stack members of trashed assets to trash too. +- Runs against multiple users in one go (comma-separated API keys). +- Lets you pick the stack parent by extension, filename pattern, regex, or size. +- Dry-run mode, stack replacement, and a confirmation-gated reset. +- Colorized, structured logs at configurable verbosity. ## Quick Links From 2bfd2af1edc974263d8935955c4f2076ab2aac6c Mon Sep 17 00:00:00 2001 From: Major Date: Sun, 7 Jun 2026 11:44:49 +0200 Subject: [PATCH 2/5] docs(config): add API permissions guide and clarify setup Add comprehensive table documenting required API key permissions for each command with explanations of why each permission is needed. Simplify formatting and clarify descriptions for easier comprehension. Consolidate best practices sections. Change-Type: docs Scope: config --- docs/api-reference/environment-variables.md | 2 +- docs/getting-started/configuration.md | 29 +++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/docs/api-reference/environment-variables.md b/docs/api-reference/environment-variables.md index 29dfacf..2087b74 100644 --- a/docs/api-reference/environment-variables.md +++ b/docs/api-reference/environment-variables.md @@ -215,7 +215,7 @@ Flexible format supporting multiple grouping strategies with OR/AND logic: ### Advanced Expression Format -Most powerful format supporting unlimited nested logical expressions with AND, OR, and NOT operations: +Supports nested logical expressions with AND, OR, and NOT (no nesting limit): ```json { diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md index c684d54..77c7d1a 100644 --- a/docs/getting-started/configuration.md +++ b/docs/getting-started/configuration.md @@ -9,6 +9,35 @@ API_KEY=your_immich_api_key API_URL=http://your_immich_server:3001/api ``` +## API key permissions + +When you create the Immich API key, the permissions you tick determine which +commands will work. If a run fails with `403 Forbidden — Missing required permission: X`, add `X` to the key and try again. + +### Minimum for `stacker` + +| Permission | Why | +| -------------- | ------------------------------------------------------------------------------------------------------- | +| `user.read` | Identify the current user (needed for multi-user and partner filtering) | +| `asset.read` | Search and fetch assets | +| `stack.read` | Read existing stacks | +| `stack.create` | Create new stacks | +| `stack.update` | Modify existing stacks (Immich enforces this even though replacement is implemented as delete + create) | +| `stack.delete` | Delete stacks (`RESET_STACKS`, `REPLACE_STACKS`, single-asset cleanup) | + +Add `album.read` if you filter by album (`ALBUM_NAMES` or `--filter-album-ids`). + +### Other commands + +`duplicates` needs nothing extra. It runs the detection client-side and +groups assets by `OriginalFileName` and `LocalDateTime`. + +`fix-trash` needs `asset.delete` on top of the stacker set. It uses +`DELETE /assets` to move matched assets to the trash. + +This list was confirmed by the maintainer in +[discussion #29](https://github.com/Majorfi/immich-stack/discussions/29#discussioncomment-16000078). + ## Run Modes Immich Stack supports two run modes: From 97a0615818abaa351ae0741e626d312b0a0f85b2 Mon Sep 17 00:00:00 2001 From: Major Date: Sun, 7 Jun 2026 11:44:54 +0200 Subject: [PATCH 3/5] docs(features): restructure sorting and criteria documentation Restructure sorting rules as numbered layers for clear precedence. Simplify descriptions of grouping modes and expression formats while maintaining technical accuracy. Improve readability through concise, direct language and better formatting. Change-Type: docs Scope: features --- docs/development/command-architecture.md | 2 +- docs/features/custom-criteria.md | 4 +- docs/features/stacking-logic.md | 114 ++++++++++-------- docs/features/whats-new-advanced-filtering.md | 7 +- 4 files changed, 73 insertions(+), 54 deletions(-) diff --git a/docs/development/command-architecture.md b/docs/development/command-architecture.md index d0ff990..e17fb47 100644 --- a/docs/development/command-architecture.md +++ b/docs/development/command-architecture.md @@ -1,6 +1,6 @@ # Command Architecture -Immich Stack uses the [Cobra](https://github.com/spf13/cobra) framework to provide a modular, extensible command-line interface. +Immich Stack uses the [Cobra](https://github.com/spf13/cobra) framework for its command-line interface. Subcommands live under `cmd/` and share helpers in `cmd/common.go`. ## Architecture Overview diff --git a/docs/features/custom-criteria.md b/docs/features/custom-criteria.md index 9ec6b19..3a42c29 100644 --- a/docs/features/custom-criteria.md +++ b/docs/features/custom-criteria.md @@ -138,7 +138,7 @@ Note: The `originalPath` splitter automatically normalizes Windows-style backsla ## Regex Configuration -The `regex` configuration allows you to extract parts of string values using regular expressions. This provides more powerful pattern matching than simple delimiter splitting: +The `regex` configuration extracts parts of string values using regular expressions. It handles patterns that delimiter splitting cannot, like capture groups or alternations: ```json { @@ -245,7 +245,7 @@ Choose **regex** for complex patterns like extracting dates, validating formats, ## Expression Format Deep Dive -The advanced expression format provides the most powerful grouping capabilities through recursive logical expressions. +The advanced expression format covers the cases the legacy and groups formats can't: recursive logical expressions with AND, OR, and NOT. ### Expression Structure diff --git a/docs/features/stacking-logic.md b/docs/features/stacking-logic.md index e4328a2..0f41730 100644 --- a/docs/features/stacking-logic.md +++ b/docs/features/stacking-logic.md @@ -2,25 +2,23 @@ ## Grouping Modes -Immich Stack supports three grouping modes with increasing complexity and power: +Immich Stack has three grouping modes, from simple to fully expressive. ### 1. Legacy Mode (Default) -- **Default Criteria:** Groups by base filename (before extension) and local capture time -- **Logic:** Simple AND operation - all criteria must match -- **Configuration:** Array format in `CRITERIA` environment variable +Groups by base filename (before extension) and local capture time. All criteria +must match (AND). Configured as a JSON array in the `CRITERIA` environment +variable. ### 2. Advanced Groups Mode -- **Multiple Strategies:** Support for multiple grouping approaches -- **Logic:** Configurable AND/OR operations per group -- **Configuration:** Object format with `"mode": "advanced"` and `groups` array +Multiple grouping strategies, each with its own AND/OR operator. Configured as +an object with `"mode": "advanced"` and a `groups` array. ### 3. Advanced Expression Mode -- **Maximum Flexibility:** Unlimited nested logical expressions -- **Logic:** Full support for AND, OR, and NOT operations with unlimited nesting -- **Configuration:** Object format with `"mode": "advanced"` and `expression` tree +Nested logical expressions with AND, OR, and NOT (no nesting limit). Configured +as an object with `"mode": "advanced"` and an `expression` tree. ## Custom Criteria @@ -28,13 +26,27 @@ Override default grouping behavior with the `--criteria` flag or `CRITERIA` envi ## Sorting -- **Parent Promotion:** Use `--parent-filename-promote` or `PARENT_FILENAME_PROMOTE` (comma-separated substrings) to promote files as stack parents -- **Empty String for Negative Matching:** Use an empty string in the promote list to prioritize files that DON'T contain any of the other substrings (e.g., `,edit` promotes unedited files first) -- **Sequence Keyword:** Use the `sequence` keyword for flexible sequential file handling (e.g., `sequence`, `sequence:4`, `sequence:IMG_`) -- **Sequence Detection:** Automatically detects numeric sequences in promote lists (e.g., `0000,0001,0002`) and uses intelligent matching for burst photos -- **Extension Promotion:** Use `--parent-ext-promote` or `PARENT_EXT_PROMOTE` (comma-separated extensions) to further prioritize -- **Extension Rank:** Built-in priority: `.jpeg` > `.jpg` > `.png` > others -- **Alphabetical:** Final tiebreaker +The sort layers, in order: + +1. Regex-based promotion when a criterion has `promote_index` configured + (see [Custom Criteria](custom-criteria.md)). +1. `--parent-filename-promote` / `PARENT_FILENAME_PROMOTE`: comma-separated + substrings, leftmost match wins. This layer also recognizes: + - An empty entry (e.g. `,edit`) as a negative match for files that contain + none of the other substrings. + - The `sequence` keyword: `sequence`, `sequence:4` (four-digit), or + `sequence:IMG_` (prefix-scoped). + - Auto-detected numeric sequences, e.g. `0000,0001,0002` extends naturally + to bursts beyond your listed range. +1. `biggestNumber` keyword (when present in `PARENT_FILENAME_PROMOTE`): + tie-break by the largest numeric suffix in the filename. +1. `--parent-ext-promote` / `PARENT_EXT_PROMOTE`: comma-separated extensions. +1. Built-in extension rank: `.jpeg` > `.jpg` > `.png` > others. +1. `biggestSize` / `smallestSize` keyword (when present in + `PARENT_FILENAME_PROMOTE`): tie-break by `exifInfo.fileSizeInByte` (largest + or smallest wins). Runs after extension rank so a smaller JPG can still beat + a larger CR2 when `.jpg` is preferred in `PARENT_EXT_PROMOTE`. +1. Alphabetical filename as the final tie-breaker. ## Examples @@ -83,7 +95,7 @@ The sequence detection works even with numbers beyond your promote list. For exa ### Sequence Keyword Examples -The `sequence` keyword provides powerful and flexible sequence handling: +The `sequence` keyword handles sequential filenames with a few variants: ```sh # Order any numeric sequence (1, 2, 10, 100, etc.) @@ -115,24 +127,27 @@ The system can detect various sequence patterns when using comma-separated numbe ## Stacking Process -1. **Fetch all stacks and assets** from Immich -1. **Determine grouping mode** based on `CRITERIA` configuration: - - **Legacy Mode:** Apply simple AND logic to array of criteria - - **Groups Mode:** Process each criteria group with configured AND/OR logic - - **Expression Mode:** Recursively evaluate nested logical expressions -1. **Group assets** into stacks using the selected mode and criteria -1. **Sort each stack** to determine the parent and children using promotion rules -1. **Apply changes** via the Immich API (create, update, or delete stacks as needed) -1. **Log all actions** and optionally run in dry-run mode for safety +1. Fetch all stacks and assets from Immich. +1. Pick the grouping mode based on `CRITERIA`: + - Legacy: apply AND logic to the criteria array. + - Groups: process each group with its configured AND/OR operator. + - Expression: recursively evaluate the nested logical tree. +1. Group assets into stacks using the selected mode and criteria. +1. Sort each stack to determine parent and children using the promotion rules. +1. Apply changes via the Immich API (create, update, or delete stacks). +1. Log all actions. In dry-run mode no API writes happen. ## Safe Operations The stacker includes several safety features: -- **Dry Run Mode:** Use `--dry-run` or `DRY_RUN=true` to simulate actions without making changes -- **Stack Replacement:** Use `--replace-stacks` or `REPLACE_STACKS=true` to replace existing stacks -- **Stack Reset:** Use `--reset-stacks` or `RESET_STACKS=true` with confirmation to delete all stacks (requires `RUN_MODE=once`) -- **Confirmation Required:** Stack reset requires explicit confirmation via `CONFIRM_RESET_STACK` +- `--dry-run` or `DRY_RUN=true` simulates the run without writing anything back + to Immich. +- `--replace-stacks` or `REPLACE_STACKS=true` rebuilds existing stacks when the + criteria pick a different membership. +- `--reset-stacks` or `RESET_STACKS=true` deletes all stacks before + re-stacking. Requires `RUN_MODE=once` and explicit confirmation via + `CONFIRM_RESET_STACK`. ## Parent Selection Edge Cases @@ -140,10 +155,10 @@ The stacker includes several safety features: When multiple promotion rules apply to different files in a group, the selection follows this strict precedence order: -1. **PARENT_FILENAME_PROMOTE list order** (left to right) -1. **PARENT_EXT_PROMOTE list order** (left to right) -1. **Built-in extension rank** (`.jpeg` > `.jpg` > `.png` > others) -1. **Alphabetical order** (case-insensitive) +1. `PARENT_FILENAME_PROMOTE` list order (left to right) +1. `PARENT_EXT_PROMOTE` list order (left to right) +1. Built-in extension rank (`.jpeg` > `.jpg` > `.png` > others) +1. Alphabetical order (case-insensitive) **Example with multiple matches**: @@ -190,9 +205,10 @@ However, alphabetical tie-breaking is also case-insensitive, so `IMG_123.jpg` an ### Unicode and Special Characters -- **Unicode characters** are supported in filename matching -- **Special regex characters** in promote strings are treated as literals (no regex escaping needed) -- **Whitespace** in filenames is preserved and matched exactly +- Unicode characters are supported in filename matching. +- Special regex characters in promote strings are treated as literals; no + escaping needed. +- Whitespace in filenames is preserved and matched exactly. **Example**: @@ -206,9 +222,9 @@ This will match files containing these exact Unicode strings. When two files have equal rank after all promotion rules, the final tie-breaker is: -1. **Original filename** (alphabetically, case-insensitive) -1. If filenames are identical: **Local date/time** (earliest first) -1. If both are identical: **Asset ID** (lexicographic order) +1. Original filename (alphabetical, case-insensitive) +1. If filenames are identical: local date/time, earliest first +1. If both are identical: asset ID, lexicographic order This ensures deterministic, reproducible parent selection across multiple runs. @@ -281,9 +297,9 @@ This has 3 levels of nesting. Each additional level multiplies the evaluation co Memory usage scales with: -1. **Asset count**: ~1KB per asset in memory -1. **Criteria complexity**: Expression trees consume additional memory per evaluation -1. **Stack size**: Larger stacks (more assets per group) increase memory overhead +1. Asset count: ~1KB per asset in memory. +1. Criteria complexity: expression trees add memory per evaluation. +1. Stack size: larger stacks (more assets per group) raise the overhead. **Guidelines**: @@ -337,9 +353,9 @@ Time-based criteria with small deltas create smaller, more numerous groups: ## Logging -The stacker provides comprehensive logging: +The stacker logs: -- Colorful, structured logs for all operations -- Clear indication of actions taken -- Error reporting with context -- Progress updates for long-running operations +- Colorized, structured output for every operation. +- The action taken on each stack (created, updated, skipped). +- Errors with the asset or stack context that caused them. +- Progress counters during long runs. diff --git a/docs/features/whats-new-advanced-filtering.md b/docs/features/whats-new-advanced-filtering.md index 82f654d..58d1fc9 100644 --- a/docs/features/whats-new-advanced-filtering.md +++ b/docs/features/whats-new-advanced-filtering.md @@ -1,6 +1,8 @@ # What’s New: Advanced Filtering & Safer Grouping -This branch introduces easier, more powerful ways to tell Immich Stack how to group your photos into stacks — with simple, copy‑pasteable examples. It focuses on new capabilities; no test/refactor details here. +This branch adds new ways to describe how Immich Stack should group your +photos, with copy-pasteable examples. It covers the user-facing changes only; +internal refactors and tests are not discussed here. ## Highlights @@ -198,4 +200,5 @@ What you’ll notice: - Advanced mode is opt‑in (`{"mode":"advanced": ...}`) - If you don’t set `mode`, legacy behavior applies -That’s it — more control, safer grouping, and easier runs from the CLI. +That's the lot. The legacy array format is still the default and nothing here +is required for existing setups. From 8321f8dcb685a6fa351aa68242e8630ba1da91e8 Mon Sep 17 00:00:00 2001 From: Major Date: Sun, 7 Jun 2026 11:45:25 +0200 Subject: [PATCH 4/5] docs(contributing): improve development guide clarity Remove unnecessary blank lines and standardize formatting throughout. Simplify descriptions and examples. Update extending section with specific guidance and cross-references to relevant documentation sections. Change-Type: docs Scope: contributing --- docs/contributing/development.md | 6 +++--- docs/development.md | 10 +++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/docs/contributing/development.md b/docs/contributing/development.md index 1c88f3f..6920774 100644 --- a/docs/contributing/development.md +++ b/docs/contributing/development.md @@ -33,9 +33,9 @@ docker run -d \ ## Extending -- **Custom Grouping:** Edit or override criteria via command-line flags or environment variables -- **Custom Promotion:** Set `--parent-filename-promote` and/or `--parent-ext-promote` for your workflow -- **API Integration:** Extend `pkg/immich/client.go` for new Immich endpoints +- Override grouping with `--criteria` / `CRITERIA` (see [Custom Criteria](../features/custom-criteria.md)). +- Tune parent selection with `--parent-filename-promote` and `--parent-ext-promote`. +- Add new Immich endpoints in `pkg/immich/client.go`. ## Library Structure diff --git a/docs/development.md b/docs/development.md index 529c67a..5733ef9 100644 --- a/docs/development.md +++ b/docs/development.md @@ -102,7 +102,6 @@ docker run -it --rm \ - Follow Go doc conventions 1. **Testing** - - Write unit tests - Use table-driven tests - Test edge cases @@ -154,7 +153,6 @@ docker run -it --rm \ - Verify documentation 1. **Pull Request** - - Push changes - Create pull request - Wait for review @@ -169,9 +167,9 @@ docker run -it --rm \ 1. **Testing** - - Write comprehensive tests - - Test edge cases - - Maintain test coverage + - Cover happy path and edge cases + - Keep test coverage from regressing + - Prefer table-driven tests for variants 1. **Documentation** @@ -180,7 +178,6 @@ docker run -it --rm \ - Document changes 1. **Performance** - - Profile code - Optimize bottlenecks - Consider memory usage @@ -200,7 +197,6 @@ docker run -it --rm \ - Sign releases 1. **Deployment** - - Push to registries - Update documentation - Announce release From 82a4ef5799f2682cd6c04c2834e90d63b3530080 Mon Sep 17 00:00:00 2001 From: Major Date: Sun, 7 Jun 2026 11:45:28 +0200 Subject: [PATCH 5/5] docs(troubleshooting): clarify error scenarios and solutions Improve clarity of error descriptions and solutions. Simplify formatting and remove redundant blank lines. Enhance readability of edge case explanations and best practices. Change-Type: docs Scope: troubleshooting --- docs/how-to/debug-parent-selection.md | 2 +- docs/troubleshooting.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/how-to/debug-parent-selection.md b/docs/how-to/debug-parent-selection.md index ff6cea0..9ef1208 100644 --- a/docs/how-to/debug-parent-selection.md +++ b/docs/how-to/debug-parent-selection.md @@ -320,7 +320,7 @@ When debugging parent selection: 1. **Start Simple**: Test with basic promotion rules first 1. **Use Dry-Run**: Always test with `DRY_RUN=true` before production -1. **Enable Debug Logs**: Use `LOG_LEVEL=debug` for detailed insights +1. **Enable Debug Logs**: Use `LOG_LEVEL=debug` to see per-asset evaluation 1. **Document Expected Behavior**: Write down what you expect before running 1. **Test Incrementally**: Add one promotion rule at a time 1. **Use Sequence Keyword**: Prefer `sequence` over comma-separated numbers diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index a5ce5ad..b195d7d 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -117,8 +117,8 @@ have on partner-owned assets). **What the tool does:** `GET /users/me`) and drops anything you don't own. When any assets are dropped, an info-level -log line reports the count (see the symptom above). When nothing is dropped — your normal -case if you have no incoming partner shares — no log line is emitted. Either way, only owned +log line reports the count (see the symptom above). When nothing is dropped (the normal +case if you have no incoming partner shares), no log line is emitted. Either way, only owned assets reach the stacking pipeline, so no partner-related write attempts ever leave the client. @@ -148,7 +148,7 @@ would otherwise match them. **Solution:** Enable `INCLUDE_VIDEOS=true` (or the CLI flag `--include-videos`). When set, every asset -fetch runs twice — once for `IMAGE` and once for `VIDEO` — and results are deduplicated. +fetch runs twice (once for `IMAGE`, once for `VIDEO`) and results are deduplicated. Existing stacking criteria (filename patterns, time deltas, regex, etc.) work on videos the same way they work on images.