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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -329,10 +329,13 @@ 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
|
||||
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
|
||||
# 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
|
||||
|
||||
pikepdf_enable_mmap()
|
||||
executor = setup_executor(plugin_manager)
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user