feat: guard the paths that assume an album entry is a still image - #360
Merged
Conversation
This was referenced Aug 15, 2026
lstein
force-pushed
the
lstein/feature/video-player-modal
branch
from
August 16, 2026 19:59
e089533 to
7efd8c2
Compare
lstein
force-pushed
the
lstein/feature/video-backend-guards
branch
2 times, most recently
from
August 16, 2026 21:43
a32b9aa to
2bf3652
Compare
lstein
force-pushed
the
lstein/feature/video-player-modal
branch
from
August 17, 2026 00:33
786c215 to
19d77e8
Compare
lstein
force-pushed
the
lstein/feature/video-backend-guards
branch
from
August 17, 2026 00:33
2bf3652 to
ca1a9a4
Compare
lstein
force-pushed
the
lstein/feature/video-player-modal
branch
from
August 17, 2026 01:56
19d77e8 to
2ae86d8
Compare
lstein
force-pushed
the
lstein/feature/video-backend-guards
branch
from
August 17, 2026 01:56
ca1a9a4 to
9b20f94
Compare
lstein
force-pushed
the
lstein/feature/video-player-modal
branch
from
August 17, 2026 02:53
2ae86d8 to
c6b1c34
Compare
lstein
force-pushed
the
lstein/feature/video-backend-guards
branch
from
August 17, 2026 02:53
9b20f94 to
935cccc
Compare
lstein
force-pushed
the
lstein/feature/video-player-modal
branch
from
August 17, 2026 02:58
c6b1c34 to
0685d6f
Compare
lstein
force-pushed
the
lstein/feature/video-backend-guards
branch
from
August 17, 2026 02:58
935cccc to
dd91950
Compare
lstein
force-pushed
the
lstein/feature/video-player-modal
branch
from
August 17, 2026 03:38
0685d6f to
6aded63
Compare
lstein
force-pushed
the
lstein/feature/video-backend-guards
branch
from
August 17, 2026 03:38
dd91950 to
bd5276a
Compare
lstein
force-pushed
the
lstein/feature/video-player-modal
branch
from
August 17, 2026 03:52
6aded63 to
3ae210e
Compare
lstein
force-pushed
the
lstein/feature/video-backend-guards
branch
2 times, most recently
from
August 17, 2026 04:06
2f85ccf to
60f945c
Compare
Six places would misbehave once the directory walk starts collecting videos. Each is fixed and pinned here, ahead of the flip. find_duplicate_clusters now excludes videos. A video's embedding describes one extracted frame, and opening frames are so often a black slate or a title card that two unrelated clips would sit at ~1.0 cosine and be reported as duplicates — which users act on by deleting. The owner had already excluded videos from dedupe as a product decision; nothing enforced it. _load_image_path raises 400 for videos. It is the choke point for /invokeai/use_ref_image, which POSTs the file to InvokeAI's /images/upload; an .mp4 would come back as an opaque 502 carrying raw upstream text. The drawer already withholds the button for videos, so this closes the API path behind it. (Recall reaches videos via the metadata path and already refused them; a test pins that too.) search_with_text_and_image returns 400 rather than 500 when the query blob is not a decodable still — a video dropped on the search panel used to surface as an opaque error from deep inside PIL. download_images_zip stores video members instead of deflating them (their streams are already compressed) and refuses selections over 2 GB. The archive is assembled entirely in memory, so twenty bookmarked 200 MB clips would have been several gigabytes resident — an OOM only reachable once videos exist. index_metadata reports image_count and video_count alongside filename_count, so the album card can explain why the number jumped rather than leaving the user to guess. Legacy indexes report video_count == 0. progress_tracker gains add_completion_warning. The completion warning was a single slot already used for the board-album "N of M missing" notice, so a video-skip warning would have silently discarded it — a run can legitimately produce both. bookmarks.js downloads a video by pointing an <a download> straight at the URL instead of buffering it into a blob, and derives its fallback extension from the real path rather than hardcoding .jpg. Tests: 14 new in test_video_guards.py. Backend 590 passed, frontend 501 passed, ruff and eslint clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lstein
force-pushed
the
lstein/feature/video-backend-guards
branch
from
August 17, 2026 10:42
60f945c to
878a453
Compare
The positive search test could not fail. test_search_still_accepts_an_ image_query never built an index, so the endpoint 500s on the missing .npz — and the assertion was `!= 400`, which a 500 satisfies. The test would have held even if the new guard rejected every valid image, which is the one thing it was there to rule out. It now builds the index and asserts 200 plus a non-empty result set. add_completion_warning silently dropped distinct notices. Dedupe tested `message not in existing` against the accumulated text, so "2 videos could not be read." is a substring of "12 videos could not be read." and was discarded — exactly the silent discard the method was added to prevent. Notices are now held as a list per album and compared whole, joined with a space only when handed to the poller, so the text the user sees is unchanged. ZIP_STORED for videos was claimed but untested. compress_type is a per-member argument that can be dropped with no other visible effect; now asserted against the returned archive (and confirmed to fail when the argument is removed). The bookmarks.js change shipped with no test at all. New bookmarks-download.test.js covers the video link path (no fetch of the media, no object URL), the still-image blob path it must not disturb, and the extension fallback — including a video whose index carries no path, where the old hardcoded .jpg would have named a clip nothing can open. The ZIP size pre-pass now applies the same validate_image_access check as the loop that writes the archive, so the ceiling is measured over files that would actually be included; counting a rejected path could refuse a selection that zips to nothing. Also: the 413 message renders sub-gigabyte ceilings as MB instead of "over the 0 GB download limit", and the dead `except HTTPException` in the query-image guard is gone — nothing inside that block raises one.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR 6 of 8 in the video-support stack. Stacked on #359.
Six places would misbehave once the directory walk starts collecting videos. Each is fixed and pinned here, ahead of the flip in PR 7.
find_duplicate_clustersexcludes videosA video's embedding describes one extracted frame, and opening frames are so often a black slate or title card that two unrelated clips sit at ~1.0 cosine and get reported as duplicates — which users act on by deleting. Videos were already excluded from dedupe as a product decision; nothing enforced it.
_load_image_pathraises 400 for videosIt's the choke point for
/invokeai/use_ref_image, which POSTs the file to InvokeAI's/images/upload. An.mp4would come back as an opaque 502 carrying raw upstream text. The drawer already withholds the button for videos (PR 3); this closes the API path behind it.Worth noting for review:
/recallgoes through_load_raw_metadata, not the file path, so it already refused videos for a different reason. There's a test pinning that too, so a future refactor routing recall through the file path stays correct.search_with_text_and_imagereturns 400, not 500A video dropped on the search panel used to surface as an opaque error from deep inside PIL.
download_images_zipVideo members are
ZIP_STOREDrather than deflated (their streams are already compressed), and selections over 2 GB get a 413. The archive is assembled entirely inBytesIO, so twenty bookmarked 200 MB clips would have been several gigabytes resident — an OOM only reachable once videos exist.index_metadatasplits the countReturns
image_countandvideo_countalongsidefilename_count, so the album card can explain why the number jumped. Legacy indexes reportvideo_count == 0.progress_tracker.add_completion_warningThe completion warning was a single slot, already used for the board-album "N of M missing" notice — a video-skip warning would have silently discarded it. A run can legitimately produce both.
set_completion_warningkeeps its replace semantics.bookmarks.jsvideo downloadPoints an
<a download>straight at the URL instead of buffering the whole file into a blob (a 200 MB clip would sit entirely in browser memory before the save dialog appeared), and derives the fallback extension from the real path rather than hardcoding.jpg.Tests: 14 new in
test_video_guards.py. Backend 590 passed, frontend 501 passed, ruff + eslint clean.🤖 Generated with Claude Code