Skip to content

Video Thumbnails — Animated Preview in Asset Bar Tooltip - #1926

Draft
vilemduha wants to merge 4 commits into
mainfrom
video_thumbnails
Draft

Video Thumbnails — Animated Preview in Asset Bar Tooltip#1926
vilemduha wants to merge 4 commits into
mainfrom
video_thumbnails

Conversation

@vilemduha

Copy link
Copy Markdown
Contributor

Summary

Adds animated video thumbnail support to the BlenderKit asset bar. When an asset has an animated thumbnail, the large tooltip image plays the video in a looped preview. Moving the mouse left/right across an asset button scrubs the video frame-by-frame (in both the tooltip and the small overlay thumbnail simultaneously). Animated small thumbnails in the asset bar buttons are opt-in via addon preferences.

What was added
Go client (client/main.go, client/structs.go)

parseThumbnails now handles animated_thumbnail file entries from the API.
For each one it builds an AnimatedThumbnailDownloadData task and queues it separately from ordinary image thumbnails.
DownloadAnimatedThumbnail performs a two-step download: first calls GetSignedURL (CDN auth), then streams the video file to disk.
Skips the download if the file is already cached.
downloadAnimatedThumbnailBatch runs all animated downloads concurrently and waits for completion before signalling Python.
New AnimatedThumbnailDownloadData struct in structs.go.
(Why separate from DownloadThumbnail? Regular thumbnails have public direct URLs; animated thumbnails live behind CDN auth and need a signed-URL step. The flows are structurally different.)

Python — video decoder (bl_ui_widgets/video/video_decoder.py)

VideoDecoder — background-thread worker that decodes a video file into raw RGBA bytes.
_probe_source() — runs ffmpeg -i to get source dimensions and fps.
_extract_frames_memory() — pipes ffmpeg's rawvideo output to stdout, reads exactly sq×sq×4 bytes per frame (where sq = min(src_w, src_h)), stores as List[bytes]. No disk I/O.
_extract_frames_disk() — fallback: extracts JPEG files to a temp dir when imageio_ffmpeg cannot be installed.
find_ffmpeg() / _ensure_imageio_ffmpeg() — auto-installs imageio-ffmpeg via pip; falls back to system PATH or Homebrew.
Module-level _decoders cache: one VideoDecoder per file path, lazily started.
get_video_decoder(filepath) — public entry point; starts and caches decoders.
Python — video widget (bl_ui_widgets/video/bl_ui_video.py)

BL_UI_Video — drop-in replacement for BL_UI_Image. Plays decoded frames on a GPU texture.
set_video(path) / clear_video() / video_is_ready() / has_video() API.
_get_or_create_texture(frame_idx) — converts raw uint8 RGBA bytes to float32 via NumPy, then creates a gpu.types.GPUTexture (format RGBA32F). Cached forever per frame; zero GPU allocation cost after the first pass.
_ensure_timer() — registers a bpy.app.timers callback that tags all VIEW_3D areas for redraw by iterating window_manager.windows (required: bpy.context.area is None inside timer callbacks).
_scrub_frame — integer set externally to override timed playback with a specific frame index.
vflip applied in ffmpeg filter because GPU textures expect bottom-left origin.
Added set_mouse_move / call_mouse_move callback pattern to BL_UI_Widget (matching existing set_mouse_enter/set_mouse_exit).
BL_UI_Button.mouse_move now calls call_mouse_move.
Python — asset bar (asset_bar_op.py)

Tooltip image is now BL_UI_Video instead of BL_UI_Image.
start_animated_thumbnail(tooltip_image, asset_data) — checks if video is on disk and calls set_video(); otherwise clears (static fallback).
update_animated_thumbnail(asset_base_id) — called by the task handler when the download completes; starts playback on the active tooltip.
Each asset button gets a BL_UI_Video overlay (video_widget) in asset_button_init.
_update_button_video(button, asset_data) — enables/disables the small animated thumbnail based on preferences.
button_mouse_move(widget, x, y) — maps the mouse X position across the button thumbnail to a frame index; sets _scrub_frame on the large tooltip video and (in parallel) on the small button overlay.
exit_button clears _scrub_frame on both when the mouse leaves.
Python — preferences (init.py)

Two new BoolProperty entries in BlenderKitAddonPreferences:

video_scrubbing (default True) — enable frame scrubbing on hover.
video_small_thumbs (default False) — show animated video in asset bar buttons (opt-in; uses more GPU memory).
Python — task dispatch (timer.py, search.py)

timer.py routes animated_thumbnail_download task type to search.handle_animated_thumbnail_download_task.
search.py calls asset_bar_operator.update_animated_thumbnail(asset_base_id).
ui.py — get_animated_thumbnail_local_path(asset_data, asset_type) returns the expected on-disk path.

Vilém Duha added 3 commits March 7, 2026 11:03
Scrubbing: moving the mouse left/right over an asset bar button scrubs the large tooltip video (and the small overlay thumbnail in parallel if enabled)
Small animated thumbnails in the asset bar (opt-in via preferences)
Timer properly triggers redraws via window_manager.windows iteration
Preferences: Video scrubbing and Animated asset bar thumbnails in addon settings
@vilemduha
vilemduha marked this pull request as ready for review March 8, 2026 05:42
@vilemduha
vilemduha requested a review from Tweekazoid March 8, 2026 05:42
@vilemduha
vilemduha marked this pull request as draft March 8, 2026 06:06
@Tweekazoid Tweekazoid assigned Tweekazoid and vilemduha and unassigned Tweekazoid Mar 9, 2026

@Tweekazoid Tweekazoid left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see issue with the file check.
It is fine right now, but maybe in future we can do hash check, and if it does not match the hash stored on web, we should download the file to prevent incomplete file downloads.

But this is for another PR, which would completely check all files.

import hashlib

def fast_hash(data: bytes) -> str:
    h = hashlib.blake2b()
    h.update(data)
    return h.hexdigest()

"video_scrubbing" option in preferences is unused
scrubbing is always active

many timers for redraw,
I would recommend one global video timer which can check active asset (hover over).
we risk massive performance hit when they all try to redraw area.
That would mean only one animated image on HOVER.
Or if not thumbnail has hover, update them one by one.
This would need testing on massive grid like 17*17 thumbnails.
And check if timer is running even if widget is not displayed/deleted.

auto-install of ffmpeg is brittle,
we should notify users that this feature is dependent on that binary, give the easy install button,
but also some environments can be locked and without ffmpeg,
even worse with ffmpeg that has limited feature set.

  • more stable option would be to use formats which do not have to be transcoded.
    (animated PNG, GIF)
    that would be more work on server side, but without the uncertainty

We can cap a widget texture cache to 24 - 48 frames to reduce vram usage spikes.
Some tiny unit test around animated thumbnail local path generation. (go and python have slightly different branching in this context)

@Tweekazoid

Copy link
Copy Markdown
Contributor

One small thing, we should be able to configure this the same way as the main/photo/wire thumbnail.

So user can pick and choose default displayed thumbnail, with auto fallback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants