633 Commits

Author SHA1 Message Date
pax
ec9e44efbe category_fetcher: extract shared tag-API params builder
Both fetch_via_tag_api and _probe_batch_api built the same params
dict (with identical lstrip/startswith credential quirks) inline.
Pulled into _build_tag_api_params so future credential-format tweaks
have one site, not two.
2026-04-15 17:27:10 -05:00
pax
24f398795b changelog: drag-start threshold bump 2026-04-14 23:27:32 -05:00
pax
3b3de35689 grid: raise drag-start threshold to 30px to match rubber band
Thumbnail file drag kicked off after only 10px of movement, which made
it too easy to start a drag when the user meant to rubber-band select
or just click-and-micro-wobble. Bumped to 30px so the gate matches the
rubber band's own threshold in `_maybe_start_rb`.

behavior change: tiny mouse movement on a thumbnail no longer starts a
file drag; you now need to drag ~30px before the OS drag kicks in.
2026-04-14 23:25:56 -05:00
pax
21bb3aa979 CHANGELOG: add [Unreleased] section for changes since v0.2.7 2026-04-14 19:05:23 -05:00
pax
289e4c2fdb release: v0.2.7 v0.2.7 2026-04-14 19:03:22 -05:00
pax
3c2aa5820d popout: remember tiled state across open/close
Popout was always reopening as floating even when it had been tiled at
close. closeEvent already persisted geometry + fullscreen, but nothing
captured the Hyprland floating/tiled bit, so the windowrule's
`float = yes` rule always won on reopen.

Now closeEvent records `_saved_tiled` from hyprctl, popout_controller
persists it as `slideshow_tiled`, and FullscreenPreview's restore path
calls the new `hyprland.settiled` helper shortly after show() to push
the window back into the layout. Saved geometry is ignored for tiled
reopens since the tile extent is the layout's concern.

behavior change: popout reopens tiled if it was tiled at close.
2026-04-14 19:01:34 -05:00
pax
a2609199bd changelog: tiled grid repaint + video thumb 10% seek 2026-04-14 15:58:36 -05:00
pax
c3efcf9f89 library: seek to 10% before capturing video thumbnail
Videos that open on a black frame (fade-in, title card, codec warmup)
produced black library thumbnails. mpv now starts at 10% with hr_seek
so the first decoded frame is past the opening. mpv clamps `start`
to valid range so very short clips still land on a real frame.
2026-04-14 15:58:33 -05:00
pax
22f09c3cdb grid: force viewport repaint on resize to fix tiled blank-out
Qt Wayland buffer goes stale after compositor-driven resize events
(Hyprland tiled geometry change). FlowLayout reflowed thumbs but the
viewport skipped paint until a scroll or click invalidated it, leaving
the grid blank. ThumbnailGrid.resizeEvent now calls viewport().update()
after reflowing so the buffer stays in sync.
2026-04-14 15:58:28 -05:00
pax
70a7903f85 changelog: VRAM fixes and popout open animation 2026-04-13 21:49:38 -05:00
pax
e004add28f popout: let open animation play on first fit
resize() and resize_and_move() gain an animate flag — when True, skip
the no_anim setprop so Hyprland's windowsIn/popin animation plays
through. Popout passes animate=_first_fit_pending so the first fit
after open animates; subsequent navigation fits still suppress anim
to avoid resize flicker.

behavior change: popout now animates in on open instead of snapping.
2026-04-13 21:49:35 -05:00
pax
9713794633 popout: explicit mpv cleanup on close to free VRAM
FullscreenPreview has no WA_DeleteOnClose and Qt's C++ dtor does
not reliably call Python-side destroy() overrides once
popout_controller drops its reference, so the popout's separate
mpv instance + NVDEC surface pool leaked until the next full
Python GC cycle. closeEvent now calls _gl_widget.cleanup()
explicitly after the state machine's CloseRequested dispatch.

behavior change from v0.2.6: popout open/close cycles no longer
stair-step VRAM upward; the popout's mpv is torn down immediately
on close instead of waiting on GC.
2026-04-13 21:04:59 -05:00
pax
860c8dcd50 video_player: drop hwdec surface pool on stop
On NVIDIA the NVDEC surface pool is the bulk of mpv's idle VRAM
footprint, and keep_open=yes plus the live GL render context pin
it for the widget lifetime. stop() now sets hwdec='no' to release
the pool while idle; play_file() re-arms hwdec='auto' before the
next loadfile so GPU decode is restored on playback.

behavior change from v0.2.6: video VRAM now releases when switching
from a video post to an image post in the same preview pane, instead
of staying pinned for the widget lifetime.
2026-04-13 21:04:54 -05:00
pax
0d75b8a3c8 changelog: 9 commits since last Unreleased update
Fixed: GL context leak on Mesa/Intel, popout teardown None guards,
category_fetcher XXE/billion-laughs rejection.
Changed: dark Fusion palette fallback, popout aspect refit on untile
(behavior change).
Removed: rolled in latest dead-var and unused-import cleanups.
2026-04-13 19:02:40 -05:00
pax
94a64dcd25 mpv_gl: make GL current before freeing mpv render context
Drivers that enforce per-context GPU resource ownership (Mesa, Intel)
leak textures and FBOs when mpv_render_context_free runs without the
owning GL context current. NVIDIA tolerates this but others do not.
2026-04-13 18:40:23 -05:00
pax
3d26e40e0f popout: guard against None centralWidget and QApplication during teardown
resizeEvent, installEventFilter, and removeEventFilter all
dereference return values that can be None during init/shutdown,
causing AttributeError crashes on edge-case lifecycle timing.
2026-04-13 18:35:22 -05:00
pax
2cdab574ca popout: refit window with correct aspect when leaving tiled layout
behavior change: navigating to a different-aspect image/video while
tiled then un-tiling now resizes the floating window to the current
content's aspect and resets the image viewer zoom. Previously the
window restored to the old floating geometry with the wrong aspect
locked.

Stash content dims on the tiled early-return in _fit_to_content, then
detect the tiled-to-floating transition via a debounced resizeEvent
check that re-runs the fit.
2026-04-12 22:18:21 -05:00
pax
57108cd0b5 info_panel: remove unnecessary f-prefix on plain string 2026-04-12 14:55:35 -05:00
pax
667ee87641 settings: remove dead get_connection_log import in _build_network_tab 2026-04-12 14:55:35 -05:00
pax
2e436af4e8 video_player: remove unused QBrush and QApplication imports 2026-04-12 14:55:34 -05:00
pax
a7586a9e43 grid: remove dead mid variable from paintEvent 2026-04-12 14:55:33 -05:00
pax
ad6f876f40 category_fetcher: reject XML responses with DOCTYPE/ENTITY declarations
User-configurable sites could send XXE or billion-laughs payloads
via tag category API responses. Reject any XML body containing
<!DOCTYPE or <!ENTITY before passing to ET.fromstring.
2026-04-12 14:55:30 -05:00
pax
56c5eac870 app_runtime: dark Fusion fallback when no system theme is detected
Systems without Trolltech.conf (bare Arch, fresh installs without a
DE) were landing on Qt's default light palette. Apply a neutral dark
Fusion palette when no system theme file exists and the palette is
still light. KDE/GNOME users keep their own palette untouched.
2026-04-12 14:43:59 -05:00
pax
11cc26479b changelog: parallel video caching, mpv library thumbnails 2026-04-12 14:31:33 -05:00
pax
14c81484c9 replace ffmpeg with mpv for library video thumbnails
behavior change: video thumbnails in the Library tab are now generated
by a headless mpv instance (vo=null, pause=True, screenshot-to-file)
instead of shelling out to ffmpeg. Resized to LIBRARY_THUMB_SIZE with
PIL. Falls back to the same placeholder on failure. ffmpeg removed
from README install commands — no longer a dependency.
2026-04-12 14:31:30 -05:00
pax
0d72b0ec8a replace stream-record with parallel httpx download for uncached videos
behavior change: clicking an uncached video now starts a full httpx
download in the background alongside mpv streaming. The cached file
is available for copy/paste as soon as the download completes, without
waiting for playback to finish. stream-record machinery removed from
video_player.py (~60 lines); on_image_done detects the streaming case
and updates path references without restarting playback.
2026-04-12 14:31:23 -05:00
pax
445d3c7a0f CHANGELOG: add [Unreleased] section for changes since v0.2.6 2026-04-12 07:23:46 -05:00
pax
0583f962d1 main_window: set minimum width on thumbnail grid
Prevents the splitter from collapsing the grid to zero width.
The minimum is one column of thumbnails (THUMB_SIZE + margins).
2026-04-12 01:15:31 -05:00
pax
3868858811 media_controller: set _cached_path for streaming videos
Streaming videos skip on_image_done (the _load coroutine returns
early), so the thumbnail's _cached_path was never set. Drag-to-copy
failed until the user navigated away and back (which went through
the cached path and hit on_image_done).

Now on_video_stream sets _cached_path to the expected cache location
immediately. Once the stream-record promotes the .part file on EOF,
drag-to-copy works without needing to change posts first.
2026-04-12 01:10:53 -05:00
pax
7ef517235f revert audio normalization feature
Neither loudnorm (EBU R128) nor dynaudnorm work well for this
use case — both are designed for continuous playback, not rapidly
switching between random short clips with wildly different levels.
2026-04-11 23:30:09 -05:00
pax
2824840b07 post_actions: refresh bookmarks grid on unsave
unsave_from_preview only refreshed the library grid when on the
library tab. Now also refreshes the bookmarks grid when on the
bookmarks tab so the saved dot clears immediately.
2026-04-11 23:24:35 -05:00
pax
61403c8acc main_window: wire loudnorm setting to video players
Read the setting at startup and apply it to the embedded preview's
video player. On settings change, toggle af=loudnorm live on all
active mpv instances (embedded + popout).

Also adds _get_all_video_players() helper for iterating both.
2026-04-11 23:22:33 -05:00
pax
2e9b99e4b8 video_player: apply loudnorm audio filter on mpv init
Reads the _loudnorm flag (set by main_window from the DB setting)
and applies af=loudnorm when mpv is first initialized.
2026-04-11 23:22:30 -05:00
pax
73206994ec settings: add audio normalization checkbox 2026-04-11 23:22:28 -05:00
pax
738e1329b8 main_window: read image dimensions for bookmark popout aspect lock
Bookmark Post objects have no width/height (the DB doesn't store
them). When opening a bookmark in the popout, read the actual
dimensions from the cached file via QImageReader so the popout
can set keep_aspect_ratio correctly. Previously images from the
bookmarks tab always got 0x0, skipping the aspect lock.
2026-04-11 23:18:06 -05:00
pax
a3cb563ae0 grid: shorten thumbnail fade-in from 200ms to 80ms 2026-04-11 23:18:06 -05:00
pax
60cf4e0beb grid: fix fade animation cleanup crashing FlowLayout.clear
The previous deleteLater on the QPropertyAnimation left a dangling
self._fade_anim reference to a dead C++ object. When the next
search called FlowLayout.clear(), calling .stop() on the dead
animation threw RuntimeError and aborted widget cleanup, leaving
stale thumbnails in the grid.

Now the finished callback nulls self._fade_anim before scheduling
deletion, so clear() never touches a dead object.
2026-04-11 23:10:54 -05:00
pax
692a0c1569 grid: clean up QPropertyAnimation after fade completes
Connect finished signal to deleteLater so the animation object
is freed instead of being retained on the widget indefinitely.
2026-04-11 23:01:45 -05:00
pax
b964a77688 cache: single-pass directory walk in eviction functions
evict_oldest and evict_oldest_thumbnails now collect paths, stats,
and sizes in one iterdir() pass instead of separate passes for
sorting, sizing, and deleting. evict_oldest also accepts a
current_bytes arg to skip a redundant cache_size_bytes() call.
2026-04-11 23:01:44 -05:00
pax
10f1b3fd10 test_mpv_options: update demuxer_max_bytes assertion to 50MiB 2026-04-11 23:01:41 -05:00
pax
5564f4cf0a video_player: pass 150MiB demuxer cap for streaming URLs
Per-file override so network video buffering stays at the
previous level despite the lower default in _mpv_options.
2026-04-11 23:01:40 -05:00
pax
b055cdd1a2 _mpv_options: reduce default demuxer buffer from 150MiB to 50MiB
150MiB is excessive for local cached file playback. Network
streaming URLs get the 150MiB cap via a per-file override in
play_file() so the fast-path buffering is unaffected.

behavior change: mpv allocates less demuxer memory for local files.
2026-04-11 23:01:38 -05:00
pax
45b87adb33 media_controller: cancel stale prefetch spirals on new click
Each prefetch_adjacent() call now bumps a generation counter.
Running spirals check the counter at each iteration and exit
when superseded. Previously, rapid clicks between posts stacked
up concurrent download loops that never cancelled, accumulating
HTTP connections and response buffers.

Also incrementally updates the search controller's cached-names
set when a download completes, avoiding a full directory rescan.

behavior change: only the most recent click's prefetch spiral
runs; older ones exit at their next iteration.
2026-04-11 23:01:35 -05:00
pax
c11cca1134 settings: remove stale restart-required label from flip layout
The setting now applies live — the "(restart required)" label was
left over from before the live-apply change.
2026-04-11 22:54:04 -05:00
pax
fa8c5b84cf media_controller: throttle auto_evict_cache to once per 30s
cache_size_bytes() does a full stat() of every file in the cache
directory. It was called on every image load and every infinite
scroll drain. Now skipped if less than 30 seconds since the last
check.

Also replace QPixmap with QImageReader in image_dimensions() to
read width/height from the file header without decoding the full
image into memory.

behavior change: cache eviction checks run at most once per 30s
instead of on every image load. Library image dimensions are read
via QImageReader (header-only) instead of QPixmap (full decode).
2026-04-11 22:49:00 -05:00
pax
c3258c1d53 post_actions: invalidate search lookup caches on bookmark/save 2026-04-11 22:48:55 -05:00
pax
3a95b6817d search_controller: cache lookup sets across infinite scroll appends
Build the cache-dir listing, bookmark ID set, and saved-post ID
set once in on_search_done and reuse in _drain_append_queue.
Previously these were rebuilt from scratch on every infinite
scroll append — a full directory listing and two DB queries per
page load.

Caches are invalidated on new search, site change, and
bookmark/save operations via invalidate_lookup_caches().
2026-04-11 22:48:54 -05:00
pax
b00f3ff95c grid: recycle decoded pixmaps for off-screen thumbnails
Release _pixmap for ThumbnailWidgets outside the visible viewport
plus a 5-row buffer zone. Re-decode from the on-disk thumbnail
cache (_source_path) when they scroll back into view. Caps decoded
thumbnail memory to the visible area instead of growing unboundedly
during infinite scroll.

behavior change: off-screen thumbnails release their decoded
pixmaps and re-decode on scroll-back. No visual difference —
the buffer zone prevents flicker.
2026-04-11 22:48:49 -05:00
pax
172fae9583 main_window: re-decode thumbnails from disk on size change
The settings thumbnail-resize path now loads from _source_path
instead of scaling from a held _source_pixmap (which no longer
exists after the grid.py change).
2026-04-11 22:40:56 -05:00
pax
12ec94b4b1 library: pass thumbnail path to set_pixmap 2026-04-11 22:40:55 -05:00