From abfa202fd33eb379982f439908fd0046c42b2dc3 Mon Sep 17 00:00:00 2001 From: Nika Siradze Date: Sun, 6 Sep 2026 15:34:23 +0400 Subject: [PATCH 1/2] Compare thumbnail candidates by identity, not by their frames When strict diversity leaves fewer candidates than were asked for, a second pass tops the list up and skips anything already chosen with `if c in selected`. A candidate is a dict carrying its own numpy frame, and `in` compares those element-wise, so the check raised "the truth value of an array with more than one element is ambiguous" and took the whole command down with it. Every caller then saw no frames at all rather than the two or three that had already been picked, and the clip got no picture. Identity is what the check meant: the same candidate, not an equal one. --- backend/services/thumbnail_ai.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/services/thumbnail_ai.py b/backend/services/thumbnail_ai.py index 3bf2114b..7dcf7817 100644 --- a/backend/services/thumbnail_ai.py +++ b/backend/services/thumbnail_ai.py @@ -343,7 +343,9 @@ def _too_similar(a: dict, b: dict) -> bool: for c in candidates: if len(selected) >= count: break - if c in selected: + # Identity, not equality: these dicts hold a numpy frame, and + # comparing two of them raises rather than answering. + if any(c is s for s in selected): continue if any(_too_similar(c, s) for s in selected): continue From c55370c6465cc1b03d2627b2d613057fef1046f0 Mon Sep 17 00:00:00 2001 From: Nika Siradze Date: Sun, 6 Sep 2026 15:34:27 +0400 Subject: [PATCH 2/2] List only the candidate frames that reached the disk cv2.imwrite answers a failed write by returning False rather than raising, and on a render box a full disk is the usual reason. The frame went into the returned list regardless, so thumbnail-options handed its caller a path to a file that is not there: the cloud worker's upload of it failed on the stat, and thumbnail-render refused it with "no frame at ...", which is the guard added with the last thumbnail fixes reporting a problem made here. Skip a frame that did not get written, and log which one it was. --- backend/services/thumbnail_ai.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/services/thumbnail_ai.py b/backend/services/thumbnail_ai.py index 7dcf7817..4b10ff0d 100644 --- a/backend/services/thumbnail_ai.py +++ b/backend/services/thumbnail_ai.py @@ -451,7 +451,13 @@ def _too_similar(a: dict, b: dict) -> bool: fh, fw = frame.shape[:2] if (fw, fh) != (target_w, target_h): frame = cv2.resize(frame, (target_w, target_h), interpolation=cv2.INTER_LANCZOS4) - cv2.imwrite(path, frame, [cv2.IMWRITE_JPEG_QUALITY, 92]) + # imwrite answers a failed write with False rather than raising, and a + # full disk is the usual reason. Listing the path regardless hands the + # renderer a frame that is not on disk, which it refuses. + if not cv2.imwrite(path, frame, [cv2.IMWRITE_JPEG_QUALITY, 92]): + log_event("thumbnail-ai", "could not write candidate frame", + level="warn", path=path) + continue results.append({ "path": path, "timestamp": c["timestamp"],