Skip to content

v1.3.0

Latest

Choose a tag to compare

@chr1shaefn3r chr1shaefn3r released this 11 Aug 21:26
8310e18

Rejecting a photo now hands its week to the runner-up, and the contact sheet shows you that happening before you commit to it.

The problem

rejects.json was applied at encode, against the already-selected frame list. That made a rejection a deletion: reject the photo that won a week, and the video lost the week. The one thing you actually wanted — the second-best photo of that week, which was sitting right there — was the one thing you could not get.

Worse, you were deciding blind. Nothing on the page said what would take over, so the loop was: reject, grow-up run, open the sheet, discover the replacement was no better, repeat. One decision per round trip, on a stage that takes a warp pass.

The fix

Rejections are applied at select, not at encode. select is the only stage that can reconsider a bucket, so a rejected pick simply loses to the runner-up and the timeline keeps its shape. Reject every candidate for a week and the week does disappear, which is presumably what you meant.

The contact sheet is now one card per bucket, and it promotes live. Each card carries its bucket's next two candidates as thumbnails. Reject the pick and the card immediately shows the next one, labelled promoted #2:

2026-03-02  #14   [reject]   promoted #2

So the grid always shows the video as it would be, and a whole pass of rejections is settled in the page before anything is re-run. Clicking a thumbnail rejects that runner-up directly, for the alternate you can already see is bad. Reject all of them and the card says bucket empty.

The alternates are warped, not raw. Judging a replacement means seeing it framed the way the video will frame it — an unaligned thumbnail answers a different question.

[select]
alternates = 2      # runner-ups per bucket to prepare; 0 turns it off

What it costs

This is the one deliberate change to an existing setup. alternates defaults to 2, so align warps three candidates per bucket instead of one — roughly 3× the warp time and 3× the frames directory. On a 150-frame timelapse that is seconds becoming a couple of minutes. Set alternates = 0 for exactly the old cost and the old page.

Nothing else about the run changes, and nothing extra is downloaded or analyzed: alternates are chosen from photos already scored.

The part that could have gone wrong

Alternates get frames rows like any other warped image. The join to selection on alternate = 0 in stage_encode is the only thing keeping them out of the render — without it a week appears three times, in a video that looks entirely plausible. That join has its own test, and the invariant is written down in CLAUDE.md alongside the other traps that produce believable-looking wrong output.

The promotion rule is driven under node against the shipped page code, for the same reason the threshold tuner is: a page that quietly stops agreeing with the pipeline still looks fine.

Nothing about an existing setup breaks

  • A config.toml with no [select] alternates keeps working; the setting arrives with a default.
  • Databases from 1.2.0 upgrade in place. selection gains an alternate column through ADDED_COLUMNS, and existing rows default to 0 — a stored selection is a pick, not a runner-up.
  • An existing rejects.json is honoured, and the contact sheet is seeded from it, so decisions made under 1.2.0 survive. The page's download replaces the whole file, so an unseeded page would silently discard every earlier decision.

After editing rejections, the re-run is grow-up select && grow-up align && grow-up encode — a warp pass, unchanged from 1.2.0.

630 tests, up from 586. 605 with 25 skipped where opencv and Pillow are absent, which is the shape CI runs, on Python 3.11, 3.12 and 3.13.

Notes

This project was written entirely by Claude (Anthropic's AI assistant, running as Claude Code) and supervised by the repository owner. The model has never had access to the Immich instance or its API keys; all verification against real photographs was done by the owner — including whether two alternates is the right number to show, which is a judgement about real faces and cannot be made from the test suite.