- Off: no prefetching
- Adjacent: simultaneous left/right/up/down (4 posts)
- Full page: ring expansion in all 8 directions
Dropdown in Settings > General replaces the old checkbox.
PIL extracts frames with durations, saves as animated GIF.
Non-animated PNG/WebP kept as-is. Converted on download and
on cache access (for already-cached files). Same pattern as
ugoira zip conversion.
Some boorus return empty/HTML responses for tag-limited queries.
All API clients now catch JSON parse errors and return empty
results instead of crashing.
Uses 'animated' as a search tag (server handles it). Removed
client-side extension filter and backfill cap increases. Fast
and doesn't hit rate limits.
Short API pages (< limit) no longer stop the scan when animated
filter is on — keeps looking through more pages. Only stops on
truly empty API response or 50 page cap.
Removed server-side tag (wastes a search slot). Client-side
filter with 20 page backfill when animated is checked (vs 5
normally) to find enough animated posts.
Client-side filter by file extension. Works with backfill and
infinite scroll. Unchecked shows everything, checked shows only
gif/mp4/webm/mkv/mov/zip(ugoira).
Optional checkbox in bold red: REMOVE ALL USER DATA (BOOKMARKS,
CACHE, LIBRARY — DATA LOSS). Unchecked by default. Deletes
%APPDATA%/booru-viewer if checked.
Hides QSpinBox arrows (break under QSS) and adds two separate
QPushButtons with triangle characters. Theme-friendly since
they're styled as normal buttons.
Cacheless mode closes the app while clients may still reference
the old event loop. Resetting to None on startup forces fresh
client creation on the new event loop.
Shared client doesn't set Referer globally since it varies per
domain. Now passed as per-request header so Gelbooru CDN doesn't
return HTML captcha pages.
Single shared httpx.AsyncClient for all BooruClient instances
(Danbooru, Gelbooru, Moebooru) with connection pooling.
E621 gets its own shared client (custom User-Agent required).
Site detection also reuses the shared client.
Eliminates per-request TLS handshakes on Windows.
- Bookmarks DB now stores tag_categories as JSON
- Migration adds column to existing favorites table
- Bookmark info panel uses stored categories directly
- Falls back to library_meta if bookmark has no categories
- Tag click in info panel: clears preview, switches to Browse, searches
- Migrate library_meta to add tag_categories column
- Info panel always updates (not gated on isVisible)
- Library info shows status bar with score/rating
- Save indicator: changed elif to if so Saved always triggers
- Bookmark info panel always populated
- tag_categories stored as JSON in library_meta table
- Library and Bookmarks info panels show categorized tags
- Bookmarks falls back to library_meta for categories
- Added Unsave from Library to bookmarks right-click menu
- library_meta table stores tags, score, rating, source per post
- Metadata saved automatically when saving from Browse
- Search box in Library tab filters by tags via DB lookup
- Works with all file types
When saving a post to library, copies the booru preview thumbnail
to thumbnails/library/ so the Library tab shows it instantly
without needing to regenerate.
When a post is activated, prefetch pauses via asyncio.Event.
The clicked post downloads at full speed without competing.
Prefetch resumes after the download completes.
Shared httpx.AsyncClient reuses TLS connections across thumbnail
downloads — avoids 40 separate TLS handshakes per page on Windows.
Score spinbox widened to 90px with explicit arrow buttons.
Sets both file URL and image data on clipboard so it works
with file managers (paste as file) and image apps (paste as
image). No more wl-copy dependency for copying.
- Blacklisting a tag or post removes matching thumbnails from
the grid without re-searching (preserves infinite scroll state)
- Video files copy as file:// URI so file managers can paste them
- Images still copy as raw data with correct MIME type
Keep _loading=True during the entire staggered append process.
Only unlock after the last post is added to the grid, preventing
reached_bottom from triggering new fetches mid-drain.
Posts from infinite scroll are queued and added individually
with 50ms delay between each, creating a smooth flowing
appearance instead of 40 empty cells appearing at once.
FlowLayout.columns now reads the scroll area viewport width
instead of its own width, which can be stale after appending
posts or scrollbar visibility changes.