feat(thumbnails): configurable thumbnail generation thread count (#64) #124

Merged
ValleyGeek merged 3 commits from 0.1.42/issue-64-thumbnail-threads into main 2026-06-22 01:42:33 +00:00
Member

Summary

  • #64generate_thumbnails_for_db() (core/thumbnail_batch.py) now decodes/resizes/encodes each batch's images across a ThreadPoolExecutor instead of strictly sequentially. New "Thumbnail generation threads" advanced setting (Settings → Advanced, default 2, range 1-16) controls the pool size; ThumbnailWorker forwards it as max_workers.
  • The executor is used solely for the pure-function generate_thumbnail_jpeg() call (file read + Pillow decode/resize/encode, no DB or Qt access). All WorkingDB writes and progress callbacks still happen only on the calling thread (the ThumbnailWorker's own QRunnable thread), preserving the Threading Contract — multiple helper threads never touch the DB, model, or any QWidget/QPixmap/QGraphics* object.

Test plan

  • ruff check / ruff format --check
  • pytest
  • PR-prep version bump 0.1.42 → 0.1.43

Refs #64. (Further follow-up tuning to this feature — a benchmark/auto-tune button and live-scan parallelization — lands in the final PR of this chain.)

Stacked on #123 — part of the 11-PR sequential chain into main.

## Summary - **#64** — `generate_thumbnails_for_db()` (`core/thumbnail_batch.py`) now decodes/resizes/encodes each batch's images across a `ThreadPoolExecutor` instead of strictly sequentially. New "Thumbnail generation threads" advanced setting (Settings → Advanced, default 2, range 1-16) controls the pool size; `ThumbnailWorker` forwards it as `max_workers`. - The executor is used solely for the pure-function `generate_thumbnail_jpeg()` call (file read + Pillow decode/resize/encode, no DB or Qt access). All WorkingDB writes and progress callbacks still happen only on the calling thread (the `ThumbnailWorker`'s own `QRunnable` thread), preserving the Threading Contract — multiple helper threads never touch the DB, model, or any `QWidget`/`QPixmap`/`QGraphics*` object. ## Test plan - [x] `ruff check` / `ruff format --check` - [x] `pytest` - [x] PR-prep version bump 0.1.42 → 0.1.43 Refs #64. (Further follow-up tuning to this feature — a benchmark/auto-tune button and live-scan parallelization — lands in the final PR of this chain.) Stacked on #123 — part of the 11-PR sequential chain into `main`.
test_changelog_section_returns_none_for_empty_unreleased and
test_dev_body_falls_back_when_unreleased_empty read the actual CHANGELOG.md
in this repo via _changelog_section()'s hardcoded REPO_ROOT, asserting
[Unreleased] is empty. That's only true between releases — any branch that
legitimately adds an Unreleased entry (e.g. a CI-only change with no version
bump) breaks these tests with no code regression. Monkeypatch REPO_ROOT to a
synthetic CHANGELOG.md so the fallback-path behavior is tested directly
instead of depending on incidental repo state.
generate_thumbnails_for_db() (core/thumbnail_batch.py) now decodes/resizes/
encodes each batch's images across a ThreadPoolExecutor instead of strictly
sequentially. New "Thumbnail generation threads" advanced setting (Settings →
Advanced, default 2, range 1-16) controls the pool size; ThumbnailWorker
forwards it as max_workers, and both call sites in main_window.py read it via
a new _thumbnail_worker_threads() helper (mirroring _dupe_tag_padding()).

Threading notes: the executor is used solely for the pure-function
generate_thumbnail_jpeg() call (file read + Pillow decode/resize/encode, no
DB or Qt access). All WorkingDB writes and on_progress/on_batch callbacks
still happen only on the calling thread (the ThumbnailWorker's own QRunnable
thread), preserving the "connections are short-lived, never shared across
threads" contract — multiple helper threads never touch the DB, model, or any
QWidget/QPixmap/QGraphics* object. Default of 1 is unchanged for any other
direct caller of generate_thumbnails_for_db() that doesn't pass max_workers.

Fixes #64
docs: PR-prep for issue 64 - bump to 0.1.43
Some checks failed
CI / lint (push) Successful in 7s
CI / test (push) Successful in 1m52s
CI / appimage (push) Successful in 8m27s
CI / build-appimage (push) Successful in 0s
CI / windows-exe (push) Failing after 10m34s
CI / build-windows-exe (push) Failing after 0s
4822f50380
Claude force-pushed 0.1.42/issue-64-thumbnail-threads from 4822f50380
Some checks failed
CI / lint (push) Successful in 7s
CI / test (push) Successful in 1m52s
CI / appimage (push) Successful in 8m27s
CI / build-appimage (push) Successful in 0s
CI / windows-exe (push) Failing after 10m34s
CI / build-windows-exe (push) Failing after 0s
to 89a59e9632 2026-06-22 00:55:29 +00:00
Compare
ValleyGeek changed target branch from 0.1.42/issue-76-build-security-scanning to main 2026-06-22 01:36:45 +00:00
Claude force-pushed 0.1.42/issue-64-thumbnail-threads from 89a59e9632 to 1b2c63099f 2026-06-22 01:37:34 +00:00 Compare
ValleyGeek deleted branch 0.1.42/issue-64-thumbnail-threads 2026-06-22 01:42:34 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ai-collab/bulk-image-organizer!124
No description provided.