Respect host-set PIL.Image.MAX_IMAGE_PIXELS in Python API

The API previously clobbered PIL.Image.MAX_IMAGE_PIXELS unconditionally
on every call, so host applications (e.g. Paperless-NGX) that configured
the PIL limit before invoking ocrmypdf.ocr() saw their setting silently
overwritten with the 250 MP default. Make max_image_mpixels default to
None and only apply the override when the caller explicitly sets it.
The CLI default of 250 MP is unchanged.

Fixes #1665
This commit is contained in:
James R. Barlow
2026-04-19 13:44:57 -07:00
parent 75714fe43e
commit 1c89cacfef
4 changed files with 57 additions and 6 deletions
+9
View File
@@ -3,6 +3,15 @@
# v17
## v17.4.2
- Fixed Python API unconditionally overriding ``PIL.Image.MAX_IMAGE_PIXELS``
when the caller did not explicitly set ``max_image_mpixels``. Host
applications (e.g. Paperless-NGX) that configure the PIL limit before
invoking ``ocrmypdf.ocr()`` now have their setting respected. The CLI
default of 250 megapixels is unchanged. {issue}`1665`
- Updated uv.lock to avoid pinning a vulnerable version of Pillow. {issue}`1666`
## v17.4.1
- Fixed RTL text extraction order in the fpdf2 renderer. Arabic lam-alef
+2 -2
View File
@@ -192,7 +192,7 @@ class OcrOptions(BaseModel):
no_overwrite: bool = False
# Advanced options
max_image_mpixels: float = 250.0
max_image_mpixels: float | None = None
pdf_renderer: str = 'auto'
ocr_engine: str = 'auto'
rasterizer: str = 'auto'
@@ -301,7 +301,7 @@ class OcrOptions(BaseModel):
@classmethod
def validate_max_image_mpixels(cls, v):
"""Validate max image megapixels."""
if v < 0:
if v is not None and v < 0:
raise ValueError("max_image_mpixels must be non-negative")
return v
+4 -1
View File
@@ -329,7 +329,10 @@ def setup_pipeline(
# Note: OcrOptions is immutable, so we can't modify options.jobs directly
# The jobs field should already be set correctly during OcrOptions creation
# Apply PIL max image pixels side effect
# Apply PIL max image pixels side effect only when explicitly requested.
# When None, leave PIL.Image.MAX_IMAGE_PIXELS as the host application
# configured it. The CLI passes its own default (250.0) via argparse.
if options.max_image_mpixels is not None:
PIL.Image.MAX_IMAGE_PIXELS = int(options.max_image_mpixels * 1_000_000)
if PIL.Image.MAX_IMAGE_PIXELS == 0:
PIL.Image.MAX_IMAGE_PIXELS = None # type: ignore
+39
View File
@@ -102,6 +102,45 @@ def test_pillow_options():
with pytest.raises(ValueError, match="max_image_mpixels must be non-negative"):
make_ocr_opts(max_image_mpixels=-1)
# Default is None, meaning "do not override host-set PIL.Image.MAX_IMAGE_PIXELS"
opts = make_ocr_opts()
assert opts.max_image_mpixels is None
def test_pillow_max_image_pixels_not_overridden_when_unset():
"""Issue #1665: respect host-set PIL.Image.MAX_IMAGE_PIXELS.
API callers (e.g. Paperless-NGX) that set PIL.Image.MAX_IMAGE_PIXELS
before invoking ocrmypdf should not have their setting clobbered when
max_image_mpixels is not explicitly passed.
"""
import PIL.Image
from ocrmypdf._pipelines._common import setup_pipeline
parser = get_parser()
pm = setup_plugin_infrastructure(plugins=[])
pm.add_options(parser=parser)
saved = PIL.Image.MAX_IMAGE_PIXELS
try:
PIL.Image.MAX_IMAGE_PIXELS = None # host disables the limit
opts = make_ocr_opts()
assert opts.max_image_mpixels is None
setup_pipeline(opts, pm)
assert PIL.Image.MAX_IMAGE_PIXELS is None
PIL.Image.MAX_IMAGE_PIXELS = 1_000_000_000 # host sets a high limit
setup_pipeline(opts, pm)
assert PIL.Image.MAX_IMAGE_PIXELS == 1_000_000_000
# When explicitly passed, it still takes effect.
opts = make_ocr_opts(max_image_mpixels=100)
setup_pipeline(opts, pm)
assert PIL.Image.MAX_IMAGE_PIXELS == 100_000_000
finally:
PIL.Image.MAX_IMAGE_PIXELS = saved
def test_output_tty():
with patch('sys.stdout.isatty', return_value=True), pytest.raises(BadArgsError):