Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/api-reference/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
6 changes: 3 additions & 3 deletions docs/contributing/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 3 additions & 7 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ docker run -it --rm \
- Follow Go doc conventions

1. **Testing**

- Write unit tests
- Use table-driven tests
- Test edge cases
Expand Down Expand Up @@ -154,7 +153,6 @@ docker run -it --rm \
- Verify documentation

1. **Pull Request**

- Push changes
- Create pull request
- Wait for review
Expand All @@ -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**

Expand All @@ -180,7 +178,6 @@ docker run -it --rm \
- Document changes

1. **Performance**

- Profile code
- Optimize bottlenecks
- Consider memory usage
Expand All @@ -200,7 +197,6 @@ docker run -it --rm \
- Sign releases

1. **Deployment**

- Push to registries
- Update documentation
- Announce release
2 changes: 1 addition & 1 deletion docs/development/command-architecture.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
4 changes: 2 additions & 2 deletions docs/features/custom-criteria.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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

Expand Down
114 changes: 65 additions & 49 deletions docs/features/stacking-logic.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,51 @@

## 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

Override default grouping behavior with the `--criteria` flag or `CRITERIA` environment variable using any of the three supported formats. See [Custom Criteria](custom-criteria.md) for complete documentation.

## 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

Expand Down Expand Up @@ -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.)
Expand Down Expand Up @@ -115,35 +127,38 @@ 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

### Multiple Promotion Rules

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**:

Expand Down Expand Up @@ -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**:

Expand All @@ -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.

Expand Down Expand Up @@ -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**:

Expand Down Expand Up @@ -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.
7 changes: 5 additions & 2 deletions docs/features/whats-new-advanced-filtering.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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.
29 changes: 29 additions & 0 deletions docs/getting-started/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion docs/how-to/debug-parent-selection.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading