Skip to content

Improve TurboJPEG detection on Windows and document fallback behavior#58

Merged
AlanRockefeller merged 2 commits intomainfrom
test
Mar 25, 2026
Merged

Improve TurboJPEG detection on Windows and document fallback behavior#58
AlanRockefeller merged 2 commits intomainfrom
test

Conversation

@AlanRockefeller
Copy link
Copy Markdown
Owner

@AlanRockefeller AlanRockefeller commented Mar 24, 2026

Summary by CodeRabbit

  • Documentation

    • Added Windows documentation describing optional TurboJPEG acceleration, DLL locations, env vars, example startup command, and troubleshooting for fallback-to-Pillow.
  • New Features

    • Windows-aware TurboJPEG discovery with prioritized candidate locations and env-var overrides.
    • Clearer runtime diagnostics and graceful fallback to Pillow when TurboJPEG is unavailable.
  • Tests

    • Added tests covering TurboJPEG initialization, candidate discovery, logging, and fallback scenarios.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 24, 2026

Walkthrough

Centralizes TurboJPEG discovery/initialization into a new faststack.imaging.turbo.create_turbojpeg() helper (Windows-aware DLL path probing and env var overrides), and updates JPEG decoding and thumbnail prefetching code to reuse that initialization with documented README additions and tests.

Changes

Cohort / File(s) Summary
Documentation
README.md
Added Windows-specific instructions for optional PyTurboJPEG (turbojpeg.dll) usage, recommended DLL location, two env vars (FASTSTACK_TURBOJPEG_LIB, TURBOJPEG_LIB) examples, command example, and troubleshooting guidance for Pillow fallback.
New Turbo helper
faststack/imaging/turbo.py
New module implementing _candidate_library_paths() and create_turbojpeg() that probes env vars and common Windows locations, attempts TurboJPEG initialization per-candidate, logs per-attempt details, and returns (TurboJPEG instance or None, bool available).
JPEG consumer
faststack/imaging/jpeg.py
Replaced local try/except TurboJPEG import/initialization with create_turbojpeg() import; continues to gate turbo use on returned availability flag and falls back to Pillow.
Thumbnail prefetcher
faststack/thumbnail_view/prefetcher.py
Switched from local TurboJPEG init to create_turbojpeg() and adjusted TJPF_RGB import source; retains Pillow fallback path and related logging.
Tests
faststack/tests/test_turbo.py
Added five pytest cases covering env-var override behavior, fallback to default loader, all-candidates-fail logging, debug logging of attempts, and missing PyTurboJPEG handling.
Changelog
ChangeLog.md
Noted Windows TurboJPEG detection/init, README addition, and Pillow fallback behavior in 1.6.1 entry.

Sequence Diagram(s)

sequenceDiagram
    participant Consumer as Consumer (jpeg/prefetcher)
    participant TurboMod as faststack.imaging.turbo
    participant OS as OS / Env / PATH
    participant PyTurbo as PyTurboJPEG
    participant Pillow as Pillow

    Consumer->>TurboMod: create_turbojpeg()
    TurboMod->>OS: build candidate list (env vars, Program Files, PATH, None)
    alt Try default loader (None candidate)
        TurboMod->>PyTurbo: TurboJPEG()  -- no args
        alt success
            PyTurbo-->>TurboMod: TurboJPEG instance
            TurboMod-->>Consumer: (instance, True)
        else failure
            PyTurbo-->>TurboMod: Exception
            TurboMod->>OS: select next candidate
        end
    else Try explicit DLL candidate
        TurboMod->>PyTurbo: TurboJPEG(path)
        alt success
            PyTurbo-->>TurboMod: TurboJPEG instance
            TurboMod-->>Consumer: (instance, True)
        else failure
            PyTurbo-->>TurboMod: Exception
            TurboMod->>OS: try next candidate
        end
    end
    alt all attempts failed or PyTurbo missing
        TurboMod-->>Consumer: (None, False)
        TurboMod-->>Consumer: log WARNING "Falling back to Pillow"
        Consumer->>Pillow: decode via Pillow
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • AlanRockefeller/faststack PR #8 — Touches the JPEG decoding pipeline; relates to centralized TurboJPEG init while PR #8 modifies thumbnail resizing behavior after decode.
  • AlanRockefeller/faststack PR #7 — Also modifies TurboJPEG usage in imaging; related to decoding/resizing and overlaps with initializer centralization.
  • AlanRockefeller/faststack PR #36 — Adds a TurboJPEG fast path for prefetch decoding; closely related to the new shared initialization and consumer changes.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main focus of the changes: improving TurboJPEG detection on Windows and documenting the fallback behavior to Pillow.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0ecf316547

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread faststack/imaging/turbo.py
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
faststack/imaging/turbo.py (1)

74-102: Consider memoizing create_turbojpeg() to avoid duplicate initialization/log noise.

create_turbojpeg() is invoked at multiple module import sites, so initialization can run more than once per process. Caching the result once will avoid repeated loader attempts and repeated warnings.

♻️ Proposed change
 from __future__ import annotations
 
 import logging
 import os
+from functools import lru_cache
 from pathlib import Path
 from typing import Optional, Tuple
@@
-def create_turbojpeg() -> Tuple[Optional["TurboJPEG"], bool]:
+@lru_cache(maxsize=1)
+def create_turbojpeg() -> Tuple[Optional["TurboJPEG"], bool]:
     """Create a TurboJPEG decoder if possible."""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@faststack/imaging/turbo.py` around lines 74 - 102, The create_turbojpeg()
function runs heavy initialization and emits logs on each import; cache its
result so it runs only once per process by memoizing the call (e.g., use a
module-level cache variable or `@functools.lru_cache` on create_turbojpeg) and
return the cached (decoder, bool) tuple on subsequent calls; ensure the cache
key is constant (no args) and keep existing behavior around TurboJPEG,
_candidate_library_paths(), and logging unchanged except that repeated
attempts/logs are suppressed after the first successful or failed
initialization.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Around line 91-99: Update the README troubleshooting section to match the
actual runtime warning emitted by faststack/imaging/turbo.py: replace the old
lines "Unable to locate turbojpeg library automatically. You may specify the
turbojpeg library path manually." with the exact message produced by turbo.py
("TurboJPEG initialization failed (... location(s) tried). Falling back to
Pillow for JPEG decoding.") and change the explanatory sentence to state that
FastStack could not initialize TurboJPEG from any discovered location and is
using Pillow instead; reference the turbo.py initialization/logging code paths
and the README header "Troubleshooting on Windows" when making the replacement
so the doc and runtime logs are consistent.

---

Nitpick comments:
In `@faststack/imaging/turbo.py`:
- Around line 74-102: The create_turbojpeg() function runs heavy initialization
and emits logs on each import; cache its result so it runs only once per process
by memoizing the call (e.g., use a module-level cache variable or
`@functools.lru_cache` on create_turbojpeg) and return the cached (decoder, bool)
tuple on subsequent calls; ensure the cache key is constant (no args) and keep
existing behavior around TurboJPEG, _candidate_library_paths(), and logging
unchanged except that repeated attempts/logs are suppressed after the first
successful or failed initialization.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5f105d85-c5e6-4db4-af6f-b857d751a122

📥 Commits

Reviewing files that changed from the base of the PR and between 148b9f7 and 0ecf316.

📒 Files selected for processing (5)
  • README.md
  • faststack/imaging/jpeg.py
  • faststack/imaging/turbo.py
  • faststack/tests/test_turbo.py
  • faststack/thumbnail_view/prefetcher.py

Comment thread README.md Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
faststack/imaging/turbo.py (2)

60-62: Normalize PATH entries before candidate construction.

On Windows, PATH entries can contain surrounding quotes/whitespace; trimming avoids malformed candidates.

Proposed change
-        for path_dir in os.getenv("PATH", "").split(os.pathsep):
-            if path_dir:
-                candidates.append(str(Path(path_dir) / "turbojpeg.dll"))
+        for raw_dir in os.getenv("PATH", "").split(os.pathsep):
+            path_dir = raw_dir.strip().strip('"')
+            if path_dir:
+                candidates.append(str(Path(path_dir) / "turbojpeg.dll"))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@faststack/imaging/turbo.py` around lines 60 - 62, The loop building DLL
candidates from PATH may produce malformed paths when PATH entries contain
surrounding quotes or whitespace; in the loop that iterates over
os.getenv("PATH", "").split(os.pathsep) (the path_dir variable) trim surrounding
quotes and whitespace (e.g., strip() and strip('"')) before constructing
Path(path_dir) / "turbojpeg.dll" and appending to candidates so only normalized
directory strings are used.

85-88: Narrow constructor error handling to avoid masking unexpected failures.

At line 85, catching Exception is broader than needed for load/probe failures. The TurboJPEG constructor is documented to raise RuntimeError when unable to locate the library automatically, and OSError when an invalid library path is provided (ctypes.CDLL failure). Prefer these specific exceptions so unexpected bugs still surface.

Proposed change
-        except Exception as exc:
+        except (OSError, RuntimeError) as exc:
             source = "default loader" if candidate is None else candidate
             failures.append(f"{source}: {exc}")
             continue
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@faststack/imaging/turbo.py` around lines 85 - 88, Narrow the broad except in
the TurboJPEG load loop to only catch the documented constructor failures:
replace the generic "except Exception as exc" with "except (RuntimeError,
OSError) as exc" so only known library-probing/CTypes errors are appended to
failures (referencing the candidate variable and failures list) and let
unexpected exceptions propagate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@faststack/imaging/turbo.py`:
- Around line 60-62: The loop building DLL candidates from PATH may produce
malformed paths when PATH entries contain surrounding quotes or whitespace; in
the loop that iterates over os.getenv("PATH", "").split(os.pathsep) (the
path_dir variable) trim surrounding quotes and whitespace (e.g., strip() and
strip('"')) before constructing Path(path_dir) / "turbojpeg.dll" and appending
to candidates so only normalized directory strings are used.
- Around line 85-88: Narrow the broad except in the TurboJPEG load loop to only
catch the documented constructor failures: replace the generic "except Exception
as exc" with "except (RuntimeError, OSError) as exc" so only known
library-probing/CTypes errors are appended to failures (referencing the
candidate variable and failures list) and let unexpected exceptions propagate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d8d3b3af-1a30-4fdf-b79f-fb4da03b5e1b

📥 Commits

Reviewing files that changed from the base of the PR and between 0ecf316 and fea6174.

📒 Files selected for processing (3)
  • ChangeLog.md
  • README.md
  • faststack/imaging/turbo.py
✅ Files skipped from review due to trivial changes (2)
  • README.md
  • ChangeLog.md

@AlanRockefeller AlanRockefeller merged commit 2968dc3 into main Mar 25, 2026
3 checks passed
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.

1 participant