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/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/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 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. 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: 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/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 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.