Immich Stack

repository·main·Indexed 42 days ago

https://github.com/majorfi/immich-stack

A tool for the Immich photo management system that automatically groups similar photos into stacks to organize burst sequences and similar shots. It includes a CLI for processing stacks, identifying duplicate assets based on filename and timestamp, and maintaining trash consistency via the fix-trash command, which can also remove orphaned RAW files.

Tokens
53.5K
Snippets
170
Records
244
Agent score
51%

What's inside Immich Stack

  1. Overview of Immich Stack

    main
    Immich Stack is a Go-based CLI and library designed to group similar photos into stacks within an Immich instance. It automates the organization of photo collections by identifying patterns like burst sequences, duplicates, and similar captures. Users can configure grouping criteria, sort order, and parent selection using either environment variables or CLI flags.
  2. Understand Stacker logging output

    main

    The stacker provides colorized, structured output for monitoring operations. Logs include:

    • Action status: Whether a stack was created, updated, or skipped.
    • Error context: Errors include the specific asset or stack context that caused the failure.
    • Progress: Counters are displayed during long-running operations.
  3. Handle Large Library 5xx Errors on GET /stacks

    main

    On libraries with >100k stacks, the Immich GET /stacks endpoint may return an HTTP 500 error because the JSON response exceeds Node.js's max string length.

    Automatic Fallback: immich-stack automatically detects this and switches to a per-asset hybrid lookup. This bypasses the failing endpoint by enumerating asset IDs and performing individual lookups.

    Performance: This fallback is slower. Expect execution time to be roughly total_assets / 800 seconds (with default concurrency of 10). For 450k assets, this is approximately 10 minutes.

    Note: A 4xx error (401, 403, 404) is not caused by this bug; it indicates an issue with your API key or URL and will not trigger a fallback.

  4. Understand the Immich Stack CLI architecture

    main

    Immich Stack is built using the Cobra framework. It operates as a multi-command CLI tool where subcommands are defined in the cmd/ directory. The hierarchy starts from the immich-stack root command, which includes several subcommands like stack (the default), duplicates, fix-trash, help, and version.

    Key architectural components include:

    • cmd/main.go: The entry point and root command setup.
    • cmd/config.go: Handles shared configuration and environment loading.
    • cmd/common.go: Contains helpers shared across all subcommands.
    • pkg/immich: The core package used for client interactions.
  5. Build complex logical expressions with AND, OR, and NOT

    main

    The Advanced Expression format allows for recursive logical trees. Each node in the tree is either a Criteria Node (a leaf containing a specific key/match) or an Operator Node (a branch containing children).

    Supported Operators:

    • AND: All children must match. Requires 1 or more children.
    • OR: At least one child must match. Requires 1 or more children.
    • NOT: The child must NOT match. Requires exactly 1 child.
    {
      "operator": "AND",
      "children": [
        {
          "operator": "OR",
          "children": [
            { "criteria": { "key": "originalFileName", "regex": { "key": "PXL_", "index": 0 } } },
            { "criteria": { "key": "originalFileName", "regex": { "key": "IMG_", "index": 0 } } }
          ]
        },
        {
          "operator": "NOT",
          "children": [{ "criteria": { "key": "isArchived" } }]
        }
      ]
    }
  6. Use Advanced Groups Format for OR logic

    main

    The Advanced Groups Format allows for more complex logic using mode: "advanced" and groups.

    Key Behavior: Union Semantics In this mode, OR groups use union semantics. Assets are grouped if they share ANY matching criteria from the OR group. This creates connected components where assets sharing any criteria are linked together.

    Example: Group by Directory OR Timestamp This configuration groups assets that share either the same directory (extracted via split) OR fall within the same 1-second time window.

    {
      "mode": "advanced",
      "groups": [
        {
          "operator": "OR",
          "criteria": [
            { "key": "originalPath", "split": { "delimiters": ["/"], "index": 2 } },
            { "key": "localDateTime", "delta": { "milliseconds": 1000 } }
          ]
        }
      ]
    }
  7. Manage memory usage based on library size

    main

    Memory usage scales with asset count (~1KB per asset), criteria complexity, and stack size. Choose your grouping mode based on your library size to stay within recommended memory bounds:

    Library SizeRecommended ModeExpected Memory
    < 10,000 assetsAny mode< 100MB
    10,000-50,000 assetsLegacy or Groups100-500MB
    50,000-100,000 assetsLegacy preferred500MB-1GB
    > 100,000 assetsLegacy only> 1GB

    Strategies for large libraries (>100k assets):

    • Use Legacy mode with simpler criteria.
    • Process in batches using WITH_ARCHIVED and WITH_DELETED filters.
    • Run the stacker during off-peak hours.
  8. Optimize performance for Expression Mode nesting

    main

    When using advanced mode with complex expressions, deep nesting increases evaluation costs. To maintain performance, follow these nesting level guidelines:

    • 1-2 levels: Negligible impact; safe for all use cases.
    • 3-4 levels: Slight increase; acceptable for most libraries.
    • 5+ levels: Noticeable impact; consider simplifying your criteria.

    Each additional level of nesting multiplies the evaluation cost for every asset in the library.

    {
      "mode": "advanced",
      "expression": {
        "operator": "AND",
        "children": [
          {
            "operator": "OR",
            "children": [
              {
                "operator": "AND",
                "children": [
                  {
                    "criteria": {
                      "key": "originalFileName",
                      "regex": { "key": "PXL_", "index": 0 }
                    }
                  },
                  {
                    "criteria": {
                      "key": "localDateTime",
                      "delta": { "milliseconds": 1000 }
                    }
                  }
                ]
              }
            ]
          }
        ]
      }
    }
  9. Security Best Practices for Immich Stack

    main

    When deploying Immich Stack, follow these security recommendations:

    • Use Docker Secrets: Instead of passing the API_KEY directly in the .env file, use API_KEY_FILE and point it to a mounted secret (e.g., API_KEY_FILE=/run/secrets/immich_api_key).
    • Principle of Least Privilege: Restrict container capabilities and run as a non-root user.
    • Version Pinning: Use specific image tags in production instead of :latest to ensure stability.
  10. Configure Immich Stack Grouping Modes

    main

    Immich Stack uses three grouping modes to determine how assets are clustered into stacks. You can configure these via the CRITERIA environment variable or the --criteria flag.

    1. Legacy Mode (Default): Groups by base filename (before extension) and local capture time. All criteria in the provided JSON array must match (AND logic).
    2. Advanced Groups Mode: Uses multiple grouping strategies, where each group has its own AND/OR operator. Configured as an object with "mode": "advanced" and a groups array.
    3. Advanced Expression Mode: Supports nested logical expressions with AND, OR, and NOT (unlimited nesting). Configured as an object with "mode": "advanced" and an expression tree.
  11. How Cron Mode execution and timing works

    main

    Cron mode operates on a stateless execution loop. Each run is independent: it fetches fresh data from the Immich API, recalculates groupings from scratch, and makes stacking decisions without remembering previous runs.

    Execution Loop: [Start] → [Run Stacker] → [Wait CRON_INTERVAL] → [Run Stacker] → [Wait] → ...

    Timing Behavior:

    • The interval timer starts after each run completes, not from the start time.
    • If a run takes longer than the CRON_INTERVAL, the next run starts immediately after completion (no runs are skipped).
    • A warning is logged if processing time exceeds 50% of the interval.

    Recommendation: Set CRON_INTERVAL to at least 2× your expected processing time to avoid overlapping runs.