Desktop booru browser. Browse, search, and save images from Danbooru, Gelbooru, e621, and more. Qt6 GUI, cross-platform, fully themeable, zero telemetry. https://git.pax.moe/pax/booru-viewer/releases
Go to file
pax 54ccc40477 Defensive hardening across core/* and popout overlay fix
Sweep of defensive hardening across the core layers plus a related popout
overlay regression that surfaced during verification.

Database integrity (core/db.py)
- Wrap delete_site, add_search_history, remove_folder, rename_folder,
  and _migrate in `with self.conn:` so partial commits can't leave
  orphan rows on a crash mid-method.
- add_bookmark re-SELECTs the existing id when INSERT OR IGNORE
  collides on (site_id, post_id). Was returning Bookmark(id=0)
  silently, which then no-op'd update_bookmark_cache_path the next
  time the post was bookmarked.
- get_bookmarks LIKE clauses now ESCAPE '%', '_', '\\' so user search
  literals stop acting as SQL wildcards (cat_ear no longer matches
  catear).

Path traversal (core/db.py + core/config.py)
- Validate folder names at write time via _validate_folder_name —
  rejects '..', os.sep, leading '.' / '~'. Permits Unicode/spaces/
  parens so existing folders keep working.
- saved_folder_dir() resolves the candidate path and refuses anything
  that doesn't relative_to the saved-images base. Defense in depth
  against folder strings that bypass the write-time validator.
- gui/bookmarks.py and gui/app.py wrap add_folder calls in try/except
  ValueError and surface a QMessageBox.warning instead of crashing.

Download safety (core/cache.py)
- New _do_download(): payloads >=50MB stream to a tempfile in the
  destination dir and atomically os.replace into place; smaller
  payloads keep the existing buffer-then-write fast path. Both
  enforce a 500MB hard cap against the advertised Content-Length AND
  the running total inside the chunk loop (servers can lie).
- Per-URL asyncio.Lock coalesces concurrent downloads of the same
  URL so two callers don't race write_bytes on the same path.
- Image.MAX_IMAGE_PIXELS = 256M with DecompressionBombError handling
  in both converters.
- _convert_ugoira_to_gif checks frame count + cumulative uncompressed
  size against UGOIRA_MAX_FRAMES / UGOIRA_MAX_UNCOMPRESSED_BYTES from
  ZipInfo headers BEFORE decompressing — defends against zip bombs.
- _convert_animated_to_gif writes a .convfailed sentinel sibling on
  failure to break the re-decode-on-every-paint loop for malformed
  animated PNGs/WebPs.
- _is_valid_media returns True (don't delete) on OSError so a
  transient EBUSY/permissions hiccup no longer triggers a delete +
  re-download loop on every access.
- _referer_for() uses proper hostname suffix matching, not substring
  `in` (imgblahgelbooru.attacker.com no longer maps to gelbooru.com).
- PIL handles wrapped in `with` blocks for deterministic cleanup.

API client retry + visibility (core/api/*)
- base.py: _request retries on httpx.NetworkError + ConnectError in
  addition to TimeoutException. test_connection no longer echoes the
  HTTP response body in the error string (it was an SSRF body-leak
  gadget when used via detect_site_type's redirect-following client).
- detect.py + danbooru.py + e621.py + gelbooru.py + moebooru.py:
  every previously-swallowed exception in search/autocomplete/probe
  paths now logs at WARNING with type, message, and (where relevant)
  the response body prefix. Debugging "the site isn't working" used
  to be a total blackout.

main_gui.py
- file_dialog_platform DB probe failure prints to stderr instead of
  vanishing.

Popout overlay (gui/preview.py + gui/app.py)
- preview.py:79,141 — setAttribute(WA_StyledBackground, True) on
  _slideshow_toolbar and _slideshow_controls. Plain QWidget parents
  silently ignore QSS `background:` declarations without this
  attribute, which is why the popout overlay strip was rendering
  fully transparent (buttons styled, bar behind them showing the
  letterbox color).
- app.py: bake _BASE_POPOUT_OVERLAY_QSS as a fallback prepended
  before the user's custom.qss in the loader. Custom themes that
  don't define overlay rules now still get a translucent black
  bar with white text + hairline borders. Bundled themes win on
  tie because their identical-specificity rules come last in the
  prepended string.
2026-04-07 17:24:19 -05:00
booru_viewer Defensive hardening across core/* and popout overlay fix 2026-04-07 17:24:19 -05:00
screenshots Update Linux screenshot for 0.2.0 2026-04-06 14:46:59 -05:00
themes Match native Qt+Fusion sizing across themed widgets (~23px uniform toolbar row), drop score +/- buttons, force score/page spinbox height to match 2026-04-07 15:42:36 -05:00
.gitattributes Treat .qss files as CSS for syntax highlighting 2026-04-05 00:51:35 -05:00
.gitignore Ignore *.dll (Windows build artifacts) 2026-04-07 08:51:05 -05:00
CHANGELOG.md Scroll tilt navigates one cell/post in grid, preview, and popout 2026-04-07 08:50:13 -05:00
LICENSE Initial release — booru image viewer with Qt6 GUI and Textual TUI 2026-04-04 06:00:50 -05:00
README.md Add BOORU_VIEWER_NO_HYPR_RULES + BOORU_VIEWER_NO_POPOUT_ASPECT_LOCK env vars for ricers with their own windowrules 2026-04-07 12:27:22 -05:00
booru-viewer.spec Fix Windows mpv DLL name: libmpv-2.dll 2026-04-06 13:56:46 -05:00
icon.ico Initial release — booru image viewer with Qt6 GUI and Textual TUI 2026-04-04 06:00:50 -05:00
icon.png Initial release — booru image viewer with Qt6 GUI and Textual TUI 2026-04-04 06:00:50 -05:00
installer.iss Bump version to 0.2.1 2026-04-07 15:44:26 -05:00
pyproject.toml Bump version to 0.2.1 2026-04-07 15:44:26 -05:00

README.md

booru-viewer

Local desktop app for browsing, searching, and saving images from booru-style imageboards.

Qt6 GUI, cross-platform (Linux + Windows), fully themeable.

If you find this useful, consider buying me a coffee:

Ko-fi

Screenshots

Windows 11 — Light Theme

Windows 11 — Light Theme

Windows 11 — Dark Theme (auto-detected)

Windows 11 — Dark Theme

Windows 10 — Light Theme

Windows 10 — Light Theme

Windows 10 — Dark Theme (auto-detected)

Windows 10 — Dark Theme

Linux — Styled via system Qt6 theme

Linux — System Qt6 theme

Supports custom styling via custom.qss — see Theming.

Features

Browsing

  • Supports Danbooru, Gelbooru, Moebooru, and e621
  • Auto-detect site API type — just paste the URL
  • Tag search with autocomplete, history dropdown, and saved searches
  • Rating and score filtering (server-side score:>=N)
  • Media type filter — dropdown: All / Animated / Video / GIF / Audio
  • Blacklisted tags and posts (client-side filtering with backfill)
  • Thumbnail grid with keyboard navigation
  • Infinite scroll — optional, auto-loads more posts at bottom
  • Start from page — jump to any page number on search
  • Page cache — prev/next loads from memory, no duplicates
  • Copy File to Clipboard — Ctrl+C, works for images and videos

Preview

  • Image viewer with zoom (scroll wheel), pan (drag), and reset (middle click)
  • GIF animation, Pixiv ugoira auto-conversion (zip to animated GIF)
  • Animated PNG/WebP auto-conversion to GIF
  • Video playback via mpv (MP4, WebM, MKV) with play/pause, seek, volume, mute, and seamless looping
  • Info panel with post details, date, clickable tags, and filetype
  • Preview toolbar — Bookmark, Save, BL Tag, BL Post, and Popout buttons above the preview panel

Popout Viewer

  • Right-click preview → "Popout" or click the Popout button in the preview toolbar
  • Arrow keys / h/j/k/l navigate posts (including during video playback)
  • , / . seek 3 seconds in videos, Space toggles play/pause
  • Floating overlay UI — toolbar and video controls auto-hide after 2 seconds, reappear on mouse move
  • F11 toggles fullscreen/windowed, Ctrl+H hides all UI, Ctrl+P privacy screen
  • Window auto-sizes to content aspect ratio; state persisted across sessions
  • Hyprland: keep_aspect_ratio prop locks window to content proportions
  • Bidirectional sync — clicking posts in the main grid updates the popout
  • Video position and player state synced between preview and popout

Bookmarks & Library

  • Bookmark posts, organize into folders
  • Three-tab layout: Browse, Bookmarks, and Library
  • Save to library (unsorted or per-folder), drag-and-drop thumbnails as files
  • Multi-select (Ctrl/Shift+Click, Ctrl+A) with bulk actions
  • Bulk context menus in both Browse and Bookmarks tabs
  • Unsave from Library available in grid, preview, and popout (only shown when post is saved)
  • Import/export bookmarks as JSON

Library

  • Dedicated tab for browsing saved files on disk
  • Folder sidebar with configurable library directory
  • Sort by date, name, or size
  • Video thumbnail generation (ffmpeg if available, placeholder fallback)
  • Unreachable directory detection
  • Inline history dropdown inside the search bar
  • Saved searches with management dialog
  • Click empty search bar to open history
  • Session cache mode clears history on exit (keeps saved searches)

Install

Windows

Download booru-viewer-setup.exe from Releases and run the installer. It installs to AppData with Start Menu and optional desktop shortcuts. To update, just run the new installer over the old one — your data in %APPDATA%\booru-viewer\ is preserved.

Windows 10 dark mode is automatically detected and applied.

Linux

Requires Python 3.11+ and pip. Most distros ship Python but you may need to install pip and the Qt6 system libraries.

Arch / CachyOS:

sudo pacman -S python python-pip qt6-base mpv ffmpeg

Ubuntu / Debian (24.04+):

sudo apt install python3 python3-pip python3-venv mpv libmpv-dev ffmpeg

Fedora:

sudo dnf install python3 python3-pip qt6-qtbase mpv mpv-libs-devel ffmpeg

Then clone and install:

git clone https://git.pax.moe/pax/booru-viewer.git
cd booru-viewer
python3 -m venv .venv
source .venv/bin/activate
pip install -e .

Run it:

booru-viewer

Or without installing: python3 -m booru_viewer.main_gui

Desktop entry: To add booru-viewer to your app launcher, create ~/.local/share/applications/booru-viewer.desktop:

[Desktop Entry]
Name=booru-viewer
Exec=/path/to/booru-viewer/.venv/bin/booru-viewer
Icon=/path/to/booru-viewer/icon.png
Type=Application
Categories=Graphics;

Hyprland integration

I daily-drive booru-viewer on Hyprland and I've baked in my own opinions on how the app should behave there. By default, a handful of hyprctl dispatches run at runtime to:

  • Restore the main window's last floating mode + dimensions on launch
  • Restore the popout's position, center-pin it around its content during navigation, and suppress F11 / fullscreen-transition flicker
  • "Prime" Hyprland's per-window floating cache at startup so a mid-session toggle to floating uses your saved dimensions
  • Lock the popout's aspect ratio to its content so you can't accidentally stretch mpv playback by dragging the popout corner

If you're a ricer with your own windowrules targeting class:^(booru-viewer)$ and you'd rather the app keep its hands off your setup, there are two independent opt-out env vars:

  • BOORU_VIEWER_NO_HYPR_RULES=1 — disables every in-code hyprctl dispatch except the popout's keep_aspect_ratio lock. Use this if you want app-side window management out of the way but you still want the popout to size itself to its content.
  • BOORU_VIEWER_NO_POPOUT_ASPECT_LOCK=1 — independently disables the popout's aspect ratio enforcement. Useful if you want to drag the popout to whatever shape you like (square, panoramic, monitor-aspect, whatever) and accept that mpv playback will letterbox or stretch to match.

For the full hands-off experience, set both:

[Desktop Entry]
Name=booru-viewer
Exec=env BOORU_VIEWER_NO_HYPR_RULES=1 BOORU_VIEWER_NO_POPOUT_ASPECT_LOCK=1 /path/to/booru-viewer/.venv/bin/booru-viewer
Icon=/path/to/booru-viewer/icon.png
Type=Application
Categories=Graphics;

Or for one-off launches from a shell:

BOORU_VIEWER_NO_HYPR_RULES=1 booru-viewer

Dependencies

  • Python 3.11+
  • PySide6 (Qt6)
  • httpx
  • Pillow
  • python-mpv
  • mpv (system package on Linux, bundled DLL on Windows)

Keybinds

Grid

Key Action
Arrow keys / h/j/k/l Navigate grid
Ctrl+A Select all
Ctrl+Click / Shift+Click Multi-select
Home / End Jump to first / last
Scroll tilt left / right Previous / next thumbnail (one cell)
Ctrl+C Copy file to clipboard
Right click Context menu

Preview

Key Action
Scroll wheel Zoom (image) / volume (video)
Scroll tilt left / right Previous / next post
Middle click / 0 Reset view
Arrow keys / h/j/k/l Navigate posts
, / . Seek 3s back / forward (video)
Space Play / pause (video, hover to activate)
Right click Context menu (bookmark, save, popout)

Popout

Key Action
Arrow keys / h/j/k/l Navigate posts
Scroll tilt left / right Previous / next post
, / . Seek 3s (video)
Space Play / pause (video)
Scroll wheel Volume up / down (video)
F11 Toggle fullscreen / windowed
Ctrl+H Hide / show UI
Ctrl+P Privacy screen
Escape / Q Close popout

Global

Key Action
Ctrl+P Privacy screen
F11 Toggle fullscreen

Adding Sites

File > Manage Sites. Enter a URL, click Auto-Detect, and save.

API credentials are optional — needed for Gelbooru and rate-limited sites.

Tested Sites

  • danbooru.donmai.us
  • gelbooru.com
  • rule34.xxx
  • safebooru.donmai.us
  • safebooru.org
  • e621.net

Theming

The app uses your OS native theme by default. To customize, copy a .qss file from the themes/ folder to your data directory as custom.qss:

  • Linux: ~/.local/share/booru-viewer/custom.qss
  • Windows: %APPDATA%\booru-viewer\custom.qss

A template is also available in Settings > Theme > Create from Template.

Included Themes

Nord Catppuccin Mocha

Gruvbox Solarized Dark

Tokyo Night Everforest

Settings

  • General — page size, thumbnail size, default site, default rating/score, prefetch mode (Off / Nearby / Aggressive), infinite scroll, popout monitor, file dialog platform
  • Cache — max cache size, max thumbnail cache, auto-evict, clear cache on exit (session-only mode)
  • Blacklist — tag blacklist with toggle, post URL blacklist
  • Paths — data directory, cache, database, configurable library directory
  • Theme — custom.qss editor, template generator, CSS guide
  • Network — connection log showing all hosts contacted this session

Data Locations

Linux Windows
Database ~/.local/share/booru-viewer/booru.db %APPDATA%\booru-viewer\booru.db
Cache ~/.local/share/booru-viewer/cache/ %APPDATA%\booru-viewer\cache\
Library ~/.local/share/booru-viewer/saved/ %APPDATA%\booru-viewer\saved\
Theme ~/.local/share/booru-viewer/custom.qss %APPDATA%\booru-viewer\custom.qss

Privacy

booru-viewer makes no connections except to the booru sites you configure. There is no telemetry, analytics, update checking, or phoning home. All data stays local on your machine.

Every outgoing request is logged in Settings > Network so you can verify this yourself — you will only see requests to the booru API endpoints and CDNs you chose to connect to.

License

MIT