From 864871b3011bae434bef676ac2fc662b9c3cee7f Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Mon, 27 Jul 2026 22:39:26 -0700 Subject: [PATCH 1/5] Add JPEG XL support Read and write JPEG XL via the pillow-jxl-plugin codec, as a new auto-discovered plugin. JXL is optimized by default and is now the preferred still conversion target ahead of WEBP and PNG. Only lossless JXL is re-encoded; lossy (XYB encoded) JXL is left alone, the same policy lossy WebP already has. The codec exposes no such signal, so picopt/pillow/jxl.py parses the codestream image header to read xyb_encoded, and refuses files carrying JPEG reconstruction data, whose re-encoding would silently destroy the ability to restore the source JPEG. New --convert-jpeg-to-jxl converts JPEG to JXL by lossless bitstream reconstruction, which is reversible byte for byte. It requires -c JXL as well: picopt otherwise never converts from a lossy source, so -c JXL alone must not sweep up a whole photo library. The codec only takes its reconstruction path when PIL knows the source filename, which it does only for path opens, so the tool opens by path and spills to a temp file for inputs that have none. That makes it work for archive members too. Three smaller things the codec needed: - It registers no save_all handler, and pil_save always passes save_all, which raised KeyError. The single-frame handler is registered for it; animated JXL is unsupported by the codec regardless. - Given no exif kwarg it reads EXIF off the opened image, which quietly defeated --strip-metadata. prepare_info now passes an empty exif. - It rejects palette and other modes, so ImageHandler gained a prepare_image hook, symmetric with prepare_info, to substitute one. Handlers can now declare CONFIG_ENABLED_KEY, a config flag that must be true for them to be probed at all. Failing it leaves the handler out of the probed handler map, which routing already reads as unavailable, so the startup conversion banner stays accurate for free. Co-Authored-By: Claude Fable 5 --- NEWS.md | 17 + README.md | 44 ++- picopt/cli.py | 11 + picopt/config/__init__.py | 2 + picopt/config/consts.py | 3 + picopt/config/handlers.py | 13 +- picopt/config/settings.py | 1 + picopt/config_default.yaml | 3 +- picopt/pillow/jxl.py | 297 ++++++++++++++++++ picopt/plugins/base/handler.py | 6 + picopt/plugins/base/image.py | 15 +- picopt/plugins/jxl.py | 231 ++++++++++++++ picopt/plugins/pil_convertible.py | 10 +- picopt/walk/detect_format.py | 9 + pyproject.toml | 5 +- tests/test_atomic_replace.py | 1 + tests/test_config_cli.py | 22 ++ tests/test_files/images/test_jxl_lossless.jxl | Bin 0 -> 92889 bytes tests/test_files/images/test_jxl_lossy.jxl | Bin 0 -> 77911 bytes tests/test_idempotency.py | 4 +- tests/test_jxl.py | 213 +++++++++++++ tests/test_jxl_detect.py | 104 ++++++ tests/test_timestamps.py | 3 +- uv.lock | 84 ++++- 24 files changed, 1081 insertions(+), 17 deletions(-) create mode 100644 picopt/pillow/jxl.py create mode 100644 picopt/plugins/jxl.py create mode 100644 tests/test_files/images/test_jxl_lossless.jxl create mode 100644 tests/test_files/images/test_jxl_lossy.jxl create mode 100644 tests/test_jxl.py create mode 100644 tests/test_jxl_detect.py diff --git a/NEWS.md b/NEWS.md index 1388bcbc..a4dc410b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,22 @@ # 📰 Picopt News +## v6.7.0 + +### Features + +- JPEG XL support, read and write, using the internal + [pillow-jxl-plugin](https://github.com/Isotr0py/pillow-jpegxl-plugin) module. +- JXL is now optimized by default. `.jxl` files that previous versions ignored + are re-encoded on a plain `picopt -r` run. Only lossless JXL is touched; lossy + (XYB encoded) JXL is left alone, as lossy WebP is. +- Lossless images convert to JXL with `-c JXL`, which now leads the preference + order for still images ahead of WEBP and PNG. +- New `--convert-jpeg-to-jxl` converts JPEG to JXL losslessly and reversibly: + the JXL stores the original JPEG bitstream and can restore it byte for byte. + It requires `-c JXL` as well, because picopt otherwise never converts from a + lossy source. Files carrying that reconstruction data are never re-encoded, so + the original JPEG stays recoverable. + ## v6.6.3 ### Fixes diff --git a/README.md b/README.md index 7086b520..94672a6f 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,10 @@ their current format. - JPEG images are optimized with MozJpeg's jpegtran. - WEBP Lossy images are not optimized. There is no current way to preserve information without running it through a lossy process again. +- Lossy (XYB encoded) JPEG XL images are not optimized, for the same reason. +- JPEG images are the one exception to the rule above: they may be converted to + JPEG XL losslessly, because JPEG XL can store the original JPEG bitstream and + restore it byte for byte. This is opt-in, see below. ### Lossless Images @@ -39,8 +43,11 @@ a great deal smaller than uncompressed bitmaps like BMP. As such the best practice is probably to convert all lossless images to WebP Lossless as now all major browsers support it. The only downside is that decoding WebP Lossless takes on average 50% more CPU than PNG. All major desktop and mobile browsers -support WEBP. WEBP is the lossless format of choice. Until perhaps JPEG XL -support arrives for browsers. +support WEBP. WEBP is the lossless format of choice, until JPEG XL support +arrives for browsers. + +Lossless JPEG XL is usually smaller still, and picopt can convert to it with +`-c JXL`. Browser support remains limited, so it is not the default target. ### Sequenced Images @@ -52,15 +59,15 @@ substitute. ### Conversion By default, picopt does not convert images between formats. You must turn on -conversion to PNG or WebP explicitly. +conversion to JXL, PNG or WebP explicitly. ## 🖼️ Formats -- By default picopt will optimize GIF, JPEG, PNG, and WEBP images. +- By default picopt will optimize GIF, JPEG, JXL, PNG, and WEBP images. - Picopt can optionally optimize SVG images, ZIP, ePub, and CBZ containers. - Picopt can convert many lossless images such as BMP, CBR, CUR, DIB, FITS, GIF, IMT, PCX, PIXAR, PNG, PPM, PSD, QOI, SGI, SPIDER, SUN, TGA, TIFF, XBM, and XPM - into PNG and WEBP. + into JXL, PNG and WEBP. - Picopt can convert Animated GIF, TIFF, and FLI into Animated PNG or WebP files. - Picopt can convert Animated GIF, TIFF, FLI, and PNG into Animated WebP files. @@ -84,6 +91,33 @@ picopt -x BMP -c WEBP big_old.bmp Picopt uses an internal mozjpeg python module to optimize JPEG images. +### JPEG XL + +Picopt reads and writes JPEG XL with the internal +[pillow-jxl-plugin](https://github.com/Isotr0py/pillow-jpegxl-plugin) module. + +Lossless JXL images are re-encoded at the highest size-targeting effort. Lossy +(XYB encoded) JXL images are left alone, as lossy WebP is. + +Converting JPEG to JPEG XL is lossless and reversible: JPEG XL stores the +original JPEG bitstream, so the exact original file can be restored later. +Because picopt otherwise never uses a lossy image as a conversion source, this +one needs its own flag in addition to the conversion target: + + + +```sh +picopt -c JXL --convert-jpeg-to-jxl photos +``` + +Two caveats. `--strip-metadata` cannot apply to this conversion, since keeping +the JPEG restorable means keeping all of its bytes. And picopt will not +re-optimize a JXL that carries JPEG reconstruction data, because re-encoding it +would silently discard the ability to restore the original JPEG. + +Animated JPEG XL is not supported in either direction; the encoder cannot read +or write it. + ### PNG & APNG Picopt uses an internal oxipng python module to optimize PNG images and convert diff --git a/picopt/cli.py b/picopt/cli.py index 1086b38d..4e3c784a 100644 --- a/picopt/cli.py +++ b/picopt/cli.py @@ -268,6 +268,17 @@ def get_arguments(params: tuple[str, ...] | None = None) -> Namespace: dest="png_max", help="Overzealously optimize pngs with -O5 and Zopfli.", ) + parser.add_argument( + "--convert-jpeg-to-jxl", + action="store_true", + default=None, + dest="convert_jpeg_to_jxl", + help=( + "Also convert JPEG images when converting to JXL. This is " + "lossless JPEG recompression: the original JPEG can be restored " + "from the JXL byte for byte. Requires -c JXL." + ), + ) parser.add_argument( "-S", "--no-symlinks", diff --git a/picopt/config/__init__.py b/picopt/config/__init__.py index b4877555..ed432d03 100644 --- a/picopt/config/__init__.py +++ b/picopt/config/__init__.py @@ -87,6 +87,7 @@ def _build_template() -> MappingTemplate: { "after": Optional(float), "bigger": bool, + "convert_jpeg_to_jxl": bool, "convert_to": Optional(Sequence(Choice(convert_to_format_strs))), "disable_programs": Sequence(str), "dry_run": bool, @@ -484,6 +485,7 @@ def _settings_from_attrdict(ad: Any) -> PicoptSettings: return PicoptSettings( after=ad.after, bigger=ad.bigger, + convert_jpeg_to_jxl=ad.convert_jpeg_to_jxl, convert_to=tuple(ad.convert_to) if ad.convert_to is not None else None, disable_programs=tuple(ad.disable_programs), dry_run=ad.dry_run, diff --git a/picopt/config/consts.py b/picopt/config/consts.py index 7c7db933..e4d499c8 100644 --- a/picopt/config/consts.py +++ b/picopt/config/consts.py @@ -19,6 +19,9 @@ TIMESTAMPS_CONFIG_KEYS: Final[frozenset[str]] = frozenset( { "bigger", + # Changes which files are converted, so it must invalidate stamps + # for the same reason convert_to does. + "convert_jpeg_to_jxl", "convert_to", "formats", "ignore", diff --git a/picopt/config/handlers.py b/picopt/config/handlers.py index a0cd494e..fb31c6dd 100644 --- a/picopt/config/handlers.py +++ b/picopt/config/handlers.py @@ -126,12 +126,23 @@ def _print_formats_config( from_list = ", ".join(sorted(sources)) logger.info(f"Converting {from_list} to {target}") + @staticmethod + def _is_handler_config_enabled(handler_cls: type[Handler], config: Subview) -> bool: + """Honor a handler's opt-in config flag, if it declares one.""" + key = handler_cls.CONFIG_ENABLED_KEY + if not key: + return True + return key in config and bool(config[key].get(bool)) + def _set_format_handler_stages( self, handler_cls: type[Handler], handler_stages: dict[type[Handler], tuple[Tool, ...]], disabled_program_names: frozenset[str], + config: Subview, ) -> None: + if not self._is_handler_config_enabled(handler_cls, config): + return stages = _select_pipeline_for_handler(handler_cls, disabled_program_names) if stages is not None: handler_stages[handler_cls] = stages @@ -194,7 +205,7 @@ def set_format_handler_map( handler_stages: dict[type[Handler], tuple[Tool, ...]] = {} for handler_cls in _enabled_handler_classes(all_format_strs): self._set_format_handler_stages( - handler_cls, handler_stages, disabled_program_names + handler_cls, handler_stages, disabled_program_names, config ) config["computed"]["handler_stages"].set(handler_stages) diff --git a/picopt/config/settings.py b/picopt/config/settings.py index 793bf5c7..b21cecc0 100644 --- a/picopt/config/settings.py +++ b/picopt/config/settings.py @@ -70,6 +70,7 @@ class PicoptSettings: # User-facing scalar options bigger: bool + convert_jpeg_to_jxl: bool dry_run: bool fail_fast: bool fail_fast_container: bool diff --git a/picopt/config_default.yaml b/picopt/config_default.yaml index 881884f7..94274828 100644 --- a/picopt/config_default.yaml +++ b/picopt/config_default.yaml @@ -1,12 +1,13 @@ picopt: after: null bigger: False + convert_jpeg_to_jxl: False convert_to: [] disable_programs: [] dry_run: False fail_fast: False fail_fast_container: False - formats: [GIF, JPEG, PNG, WEBP] + formats: [GIF, JPEG, JXL, PNG, WEBP] ignore: [] ignore_defaults: True jobs: 0 diff --git a/picopt/pillow/jxl.py b/picopt/pillow/jxl.py new file mode 100644 index 00000000..7ec724c9 --- /dev/null +++ b/picopt/pillow/jxl.py @@ -0,0 +1,297 @@ +""" +JPEG XL support: PIL registration and lossless detection. + +Importing this module registers the ``pillow-jxl-plugin`` codec with Pillow +(the plugin registers itself as an import side effect) and patches in a +``save_all`` handler the plugin does not install itself. picopt's +``ImageHandler.pil_save`` always calls ``Image.save(save_all=True)``, which +raises ``KeyError`` for any format missing from Pillow's ``SAVE_ALL`` +registry. JPEG XL animation is not supported by the plugin at all, so +reusing the single-frame save handler is exactly right. + +The rest of this module answers one question detect_format needs and the +plugin cannot: may picopt losslessly re-encode this JXL? That is false for +two unrelated reasons, and the caller does not care which: + +* **XYB-encoded (lossy) files.** Re-encoding them losslessly is safe for + pixels but pointless — the result is always far larger and gets discarded + by the size gate — so it is wasted work on every run. +* **JPEG-reconstruction files.** A JXL carrying a ``jbrd`` box can restore + its source JPEG byte for byte. Re-encoding preserves the pixels but drops + that box, silently destroying the reconstruction data. + +Neither signal is exposed by the plugin's decoder, so the codestream header +is parsed here. This should be a part of Pillow. +""" + +from __future__ import annotations + +from contextlib import suppress +from importlib import import_module +from typing import TYPE_CHECKING, Final + +from PIL import Image + +if TYPE_CHECKING: + from typing import BinaryIO + +# Imported for its side effect alone: pillow_jxl registers the codec with +# Pillow at import time and exposes nothing picopt calls directly, so a plain +# `import` reads as unused and gets stripped by autofixers. +# Suppressed so picopt still runs when the optional codec is absent; the +# handlers then probe as unavailable and JXL files are simply skipped. +with suppress(ImportError): + import_module("pillow_jxl") + +JXL_FORMAT_STR: Final[str] = "JXL" + +# Pillow's own registry is the source of truth for whether the import above +# succeeded. +if JXL_FORMAT_STR in Image.SAVE and JXL_FORMAT_STR not in Image.SAVE_ALL: + Image.register_save_all(JXL_FORMAT_STR, Image.SAVE[JXL_FORMAT_STR]) + +_CODESTREAM_SIG: Final[bytes] = b"\xff\x0a" +_CONTAINER_SIG: Final[bytes] = b"\x00\x00\x00\x0cJXL \x0d\x0a\x87\x0a" +_JPEG_RECONSTRUCTION_BOX: Final[bytes] = b"jbrd" +_BROTLI_BOX: Final[bytes] = b"brob" +_CODESTREAM_BOX: Final[bytes] = b"jxlc" +_PARTIAL_CODESTREAM_BOX: Final[bytes] = b"jxlp" +# The partial-codestream box prefixes its payload with a 4 byte sequence index. +_JXLP_INDEX_LEN: Final[int] = 4 +_BOX_HEADER_LEN: Final[int] = 8 +_BOX_EXT_HEADER_LEN: Final[int] = 16 +# The image header is a few dozen bytes; this is a generous ceiling that also +# bounds how much of a split codestream is buffered. +_MAX_HEADER_BYTES: Final[int] = 1024 +# A sanity bound: JXL permits at most 256 extra channels, and a parse that +# claims more has certainly desynchronized. +_MAX_EXTRA_CHANNELS: Final[int] = 256 + +# U32 field distributions, as (bits, offset) pairs or a bare literal value. +# A 2 bit selector chooses among the four alternatives. +_U32_DIMENSION: Final = ((9, 1), (13, 1), (18, 1), (30, 1)) +_U32_ENUM: Final = (0, 1, 2, (6, 3)) +_U32_BITS_PER_SAMPLE: Final = (8, 10, 12, (6, 1)) +_U32_FLOAT_BITS_PER_SAMPLE: Final = (32, 16, 24, (6, 1)) +_U32_NUM_EXTRA_CHANNELS: Final = (0, 1, (4, 2), (12, 1)) +_U32_DIM_SHIFT: Final = (0, 3, 4, (3, 1)) +_U32_NAME_LEN: Final = (0, (4, 1), (5, 17), (10, 49)) +_U32_PREVIEW_DIV8: Final = (16, 32, (5, 1), (9, 33)) +_U32_PREVIEW: Final = ((6, 1), (8, 65), (10, 321), (12, 1345)) +_U32_TPS_NUMERATOR: Final = (100, 1000, (10, 1), (30, 1)) +_U32_TPS_DENOMINATOR: Final = (1, 1001, (8, 1), (10, 1)) +_U32_NUM_LOOPS: Final = (0, (3, 0), (16, 0), (32, 0)) +_U32_CFA_CHANNEL: Final = (1, (2, 0), (4, 3), (8, 19)) + +_EXTRA_CHANNEL_ALPHA: Final[int] = 0 +_EXTRA_CHANNEL_SPOT_COLOR: Final[int] = 3 +_EXTRA_CHANNEL_CFA: Final[int] = 4 +_SPOT_COLOR_BITS: Final[int] = 32 * 4 + + +class _TruncatedError(Exception): + """The header ended before the parse finished.""" + + +class _BitReader: + """Reads the JXL bitstream, which packs bits LSB-first within each byte.""" + + def __init__(self, data: bytes) -> None: + self._data = data + self._pos = 0 + + def bit(self) -> int: + """Read a single bit.""" + byte_index, bit_index = divmod(self._pos, 8) + if byte_index >= len(self._data): + raise _TruncatedError + self._pos += 1 + return (self._data[byte_index] >> bit_index) & 1 + + def bits(self, count: int) -> int: + """Read an unsigned integer of ``count`` bits.""" + value = 0 + for index in range(count): + value |= self.bit() << index + return value + + def u32(self, distributions: tuple) -> int: + """Read a U32: a 2 bit selector picks one of four encodings.""" + distribution = distributions[self.bits(2)] + if isinstance(distribution, int): + return distribution + bit_count, offset = distribution + return self.bits(bit_count) + offset + + +def _skip_size_header(reader: _BitReader) -> None: + small = reader.bit() + if small: + reader.bits(5) + else: + reader.u32(_U32_DIMENSION) + if reader.bits(3) == 0: + if small: + reader.bits(5) + else: + reader.u32(_U32_DIMENSION) + + +def _skip_preview_header(reader: _BitReader) -> None: + div8 = reader.bit() + distributions = _U32_PREVIEW_DIV8 if div8 else _U32_PREVIEW + reader.u32(distributions) + if reader.bits(3) == 0: + reader.u32(distributions) + + +def _skip_animation_header(reader: _BitReader) -> None: + reader.u32(_U32_TPS_NUMERATOR) + reader.u32(_U32_TPS_DENOMINATOR) + reader.u32(_U32_NUM_LOOPS) + reader.bit() # have_timecodes + + +def _skip_bit_depth(reader: _BitReader) -> None: + if reader.bit(): # floating point + reader.u32(_U32_FLOAT_BITS_PER_SAMPLE) + reader.bits(4) # exponent bits - 1 + else: + reader.u32(_U32_BITS_PER_SAMPLE) + + +def _skip_extra_channel_info(reader: _BitReader) -> None: + if reader.bit(): # all default: an 8 bit alpha channel + return + channel_type = reader.u32(_U32_ENUM) + _skip_bit_depth(reader) + reader.u32(_U32_DIM_SHIFT) + reader.bits(8 * reader.u32(_U32_NAME_LEN)) # name + if channel_type == _EXTRA_CHANNEL_ALPHA: + reader.bit() # alpha_associated + elif channel_type == _EXTRA_CHANNEL_SPOT_COLOR: + reader.bits(_SPOT_COLOR_BITS) + elif channel_type == _EXTRA_CHANNEL_CFA: + reader.u32(_U32_CFA_CHANNEL) + + +def _skip_extra_fields(reader: _BitReader) -> None: + reader.bits(3) # orientation + if reader.bit(): + _skip_size_header(reader) # intrinsic size + if reader.bit(): + _skip_preview_header(reader) + if reader.bit(): + _skip_animation_header(reader) + + +def _is_xyb_encoded(codestream: bytes) -> bool | None: + """ + Parse the image header far enough to read the ``xyb_encoded`` flag. + + XYB is a lossy colour transform, so an XYB-encoded file cannot be + lossless. Returns None when the header cannot be parsed, which the + caller treats as "do not touch this file". + """ + if not codestream.startswith(_CODESTREAM_SIG): + return None + reader = _BitReader(codestream[len(_CODESTREAM_SIG) :]) + try: + _skip_size_header(reader) + if reader.bit(): # ImageMetadata all_default; xyb_encoded defaults true + return True + if reader.bit(): # extra_fields + _skip_extra_fields(reader) + _skip_bit_depth(reader) + reader.bit() # modular_16bit_buffers + extra_channel_count = reader.u32(_U32_NUM_EXTRA_CHANNELS) + if extra_channel_count > _MAX_EXTRA_CHANNELS: + return None + for _ in range(extra_channel_count): + _skip_extra_channel_info(reader) + except (_TruncatedError, IndexError): + return None + else: + return bool(reader.bit()) + + +def _read_box_header(buffer: BinaryIO) -> tuple[bytes, int, int] | None: + """Return (box_type, total_box_size, header_length) or None at the end.""" + header = buffer.read(_BOX_HEADER_LEN) + if len(header) < _BOX_HEADER_LEN: + return None + size = int.from_bytes(header[:4], "big") + box_type = header[4:8] + header_len = _BOX_HEADER_LEN + if size == 1: + extended = buffer.read(_BOX_EXT_HEADER_LEN - _BOX_HEADER_LEN) + if len(extended) < _BOX_EXT_HEADER_LEN - _BOX_HEADER_LEN: + return None + size = int.from_bytes(extended, "big") + header_len = _BOX_EXT_HEADER_LEN + if size and size < header_len: + return None + return box_type, size, header_len + + +def _walk_container(buffer: BinaryIO) -> tuple[bytes, bool]: + """ + Walk the ISO BMFF boxes, returning (codestream_head, has_reconstruction). + + Payloads are skipped with seeks; only the leading codestream bytes are + buffered, and only up to the header ceiling. + """ + codestream = b"" + has_reconstruction = False + buffer.seek(len(_CONTAINER_SIG)) + while True: + box = _read_box_header(buffer) + if box is None: + break + box_type, size, header_len = box + payload_start = buffer.tell() + if box_type == _JPEG_RECONSTRUCTION_BOX: + has_reconstruction = True + elif box_type == _BROTLI_BOX: + # A brotli-compressed box names its inner type in the first four + # payload bytes. + has_reconstruction = has_reconstruction or ( + buffer.read(4) == _JPEG_RECONSTRUCTION_BOX + ) + elif box_type in (_CODESTREAM_BOX, _PARTIAL_CODESTREAM_BOX) and ( + len(codestream) < _MAX_HEADER_BYTES + ): + if box_type == _PARTIAL_CODESTREAM_BOX: + buffer.seek(payload_start + _JXLP_INDEX_LEN) + codestream += buffer.read(_MAX_HEADER_BYTES - len(codestream)) + if size == 0: + # A zero size means the box runs to the end of the file. + break + buffer.seek(payload_start + size - header_len) + return codestream, has_reconstruction + + +def is_lossless(input_buffer: BinaryIO) -> bool: + """ + Report whether picopt may losslessly re-encode this JXL. + + False for lossy (XYB-encoded) files, for files carrying JPEG + reconstruction data, and for anything that cannot be parsed. Leaves the + buffer position at 0; the caller owns closing it. + """ + try: + input_buffer.seek(0) + signature = input_buffer.read(len(_CONTAINER_SIG)) + if signature.startswith(_CODESTREAM_SIG): + input_buffer.seek(0) + codestream = input_buffer.read(_MAX_HEADER_BYTES) + has_reconstruction = False + elif signature == _CONTAINER_SIG: + codestream, has_reconstruction = _walk_container(input_buffer) + else: + return False + if has_reconstruction: + return False + return _is_xyb_encoded(codestream) is False + finally: + input_buffer.seek(0) diff --git a/picopt/plugins/base/handler.py b/picopt/plugins/base/handler.py index 32eac2bc..57cfb651 100644 --- a/picopt/plugins/base/handler.py +++ b/picopt/plugins/base/handler.py @@ -56,6 +56,11 @@ class Handler(ABC): - ``PIPELINE``: tuple-of-tuples of :class:`Tool` instances. The outer tuple is sequential pipeline tiers; each inner tuple is alternatives for that tier. This replaces the old ``PROGRAMS`` mechanism. + - ``CONFIG_ENABLED_KEY``: name of a boolean config option that must be + true for this handler to run at all. Empty means always enabled. + Handlers that fail the check are simply left out of the probed + handler map, which the routing layer already reads as "unavailable" + and falls through. """ SUFFIXES: tuple[str, ...] = () @@ -65,6 +70,7 @@ class Handler(ABC): ) INPUT_FILE_FORMATS: frozenset[FileFormat] = frozenset() PIPELINE: tuple[tuple[Tool, ...], ...] = () + CONFIG_ENABLED_KEY: str = "" # ------------------------------------------------------------------ init diff --git a/picopt/plugins/base/image.py b/picopt/plugins/base/image.py index 798f89d0..e40080dd 100644 --- a/picopt/plugins/base/image.py +++ b/picopt/plugins/base/image.py @@ -84,6 +84,19 @@ def prepare_info(self, format_str: str) -> MappingProxyType[str, Any]: } return MappingProxyType(info) + # ---------------------------------------------------------- image munging + + def prepare_image(self, image: Image.Image, format_str: str) -> Image.Image: + """ + Return the image to hand to ``Image.save``. + + A hook for encoders that accept only a subset of PIL modes; the + default saves whatever was opened. Symmetric with + :meth:`prepare_info`, which does the same job for the info dict. + """ + del format_str + return image + # ----------------------------------------------------------- pil_save def pil_save( @@ -106,7 +119,7 @@ def pil_save( info = self.prepare_info(format_str) output_buffer = BytesIO() with Image.open(input_buffer) as image: - image.save( + self.prepare_image(image, format_str).save( output_buffer, format_str, save_all=True, diff --git a/picopt/plugins/jxl.py b/picopt/plugins/jxl.py new file mode 100644 index 00000000..af3a6212 --- /dev/null +++ b/picopt/plugins/jxl.py @@ -0,0 +1,231 @@ +""" +JPEG XL format plugin. + +Owns: JXL (still). Tool: the ``pillow-jxl-plugin`` Pillow codec, driven +in-process. Two handlers with different jobs: + +* :class:`JxlLossless` re-encodes lossless JXL in place and is the target + for every lossless still conversion. Only files that + :func:`picopt.pillow.jxl.is_lossless` accepts are routed here, so lossy + (XYB-encoded) JXL is left alone exactly as lossy WebP is. +* :class:`JxlFromJpeg` converts JPEG to JXL by lossless bitstream + reconstruction, which is reversible: the original JPEG can be restored + byte for byte. It is doubly opt-in — ``--convert-to JXL`` *and* + ``--convert-jpeg-to-jxl`` — because picopt otherwise never uses a lossy + format as a conversion source. + +Animation is deliberately absent: the codec can neither read nor write +animated JXL, so no animated route points here in either direction. +""" + +from __future__ import annotations + +import os +from io import BytesIO +from tempfile import mkstemp +from types import MappingProxyType +from typing import TYPE_CHECKING, Any, Final + +from loguru import logger +from PIL import Image +from typing_extensions import override + +from picopt.pillow.jxl import JXL_FORMAT_STR +from picopt.plugins.base import ( + Handler, + ImageHandler, + InternalTool, + PILSaveTool, + Plugin, + Route, + Tool, +) +from picopt.plugins.base.format import FileFormat +from picopt.plugins.gif import Gif +from picopt.plugins.jpeg import Jpeg +from picopt.plugins.png import Png +from picopt.plugins.webp.static import WebPLossless + +if TYPE_CHECKING: + from pathlib import Path + from typing import BinaryIO + +# effort 9 is the slowest setting that still targets size; 10 exists but +# costs disproportionately more time for a fraction of a percent. +_EFFORT: Final[int] = 9 + +# The modes pillow-jxl-plugin can encode. Anything else raises +# NotImplementedError, so it is converted first. +_VALID_MODES: Final[frozenset[str]] = frozenset({"RGB", "RGBA", "L", "LA", "I;16", "F"}) +# Substitutes for the modes the encoder rejects. Palette images need the +# transparency check, so they are resolved in code rather than here. +_MODE_SUBSTITUTES: Final[MappingProxyType[str, str]] = MappingProxyType( + { + "1": "L", + "I": "I;16", + "PA": "RGBA", + "CMYK": "RGB", + "YCbCr": "RGB", + "LAB": "RGB", + "HSV": "RGB", + } +) + + +# --------------------------------------------------------------------------- +# Tools +# --------------------------------------------------------------------------- + + +class JpegXlReconstructTool(InternalTool): + """ + Transcode a JPEG into JXL, preserving the original JPEG bitstream. + + The codec only takes its reconstruction path when PIL knows the source + *filename*, and PIL sets that only when opened from a path — never from + a file object. So this tool opens by path, spilling to a temp file for + inputs that have none (archive members). Without that, the encode + silently falls back to a plain pixel encode: still lossless, but no + longer reversible and usually larger. + """ + + name = "pil2jxl_jpeg" + module_name = "pillow_jxl" + PACKAGE_NAME = "pillow-jxl-plugin" + + _SAVE_KWARGS: Final[MappingProxyType[str, Any]] = MappingProxyType( + { + "lossless_jpeg": True, + # Belt and braces: if the reconstruction path is ever skipped, + # this keeps the pixel fallback lossless instead of quality 90. + "lossless": True, + "effort": _EFFORT, + } + ) + + @override + def run_stage(self, handler: Handler, buf: BinaryIO) -> BinaryIO: + input_path, is_tmp = self._input_path(handler, buf) + try: + output_buffer = BytesIO() + with Image.open(input_path) as image: + image.save(output_buffer, JXL_FORMAT_STR, **self._SAVE_KWARGS) + finally: + if is_tmp: + input_path.unlink(missing_ok=True) + return output_buffer + + @staticmethod + def _input_path(handler: Handler, buf: BinaryIO) -> tuple[Path, bool]: + """Return a real path for the input and whether it is ours to delete.""" + path = handler.path_info.path + if path is not None and not isinstance(buf, BytesIO): + return path, False + working_path = handler.get_working_path() + fd, tmp_str = mkstemp(prefix=working_path.name + ".", suffix=".jpg") + os.close(fd) + tmp_path = type(working_path)(tmp_str) + buf.seek(0) + with tmp_path.open("wb") as tmp_file: + tmp_file.write(buf.read()) + buf.seek(0) + return tmp_path, True + + +# --------------------------------------------------------------------------- +# Handlers +# --------------------------------------------------------------------------- + + +class JxlLossless(ImageHandler): + """Lossless still JXL handler.""" + + OUTPUT_FORMAT_STR = JXL_FORMAT_STR + OUTPUT_FILE_FORMAT = FileFormat(JXL_FORMAT_STR, lossless=True, animated=False) + # OUTPUT_FILE_FORMAT is deliberately absent: pil_save short-circuits when + # the input is already an acceptable input format, which for a + # single-tier JXL -> JXL pipeline would make re-optimization a no-op. + # Png is listed so this handler is probed whenever PNG is requested, + # which is what makes `-f PNG -c JXL` work. + INPUT_FILE_FORMATS = frozenset({Png.OUTPUT_FILE_FORMAT}) + SUFFIXES: tuple[str, ...] = (".jxl",) + + PIL2_KWARGS: MappingProxyType[str, Any] = MappingProxyType( + {"lossless": True, "effort": _EFFORT} + ) + PIPELINE: tuple[tuple[Tool, ...], ...] = ( + (PILSaveTool(target_format_str=JXL_FORMAT_STR, name="pil2jxl"),), + ) + + @override + def prepare_info(self, format_str: str) -> MappingProxyType[str, Any]: + """Suppress the encoder's own EXIF fallback when stripping metadata.""" + info = super().prepare_info(format_str) + if format_str == JXL_FORMAT_STR and not self.config.keep_metadata: + # Given no exif kwarg, the encoder reads EXIF off the opened + # image itself, which would quietly defeat --strip-metadata. + # An empty value takes the kwarg branch and writes no EXIF box. + info = MappingProxyType({**info, "exif": b""}) + return info + + def _substitute_mode(self, image: Image.Image) -> str | None: + """Return a mode the encoder accepts, or None if it accepts this one.""" + if image.mode in _VALID_MODES: + return None + if image.mode == "P": + return "RGBA" if "transparency" in image.info else "RGB" + return _MODE_SUBSTITUTES.get(image.mode, "RGB") + + @override + def prepare_image(self, image: Image.Image, format_str: str) -> Image.Image: + """Convert modes the JXL encoder cannot represent.""" + if format_str != JXL_FORMAT_STR: + return image + mode = self._substitute_mode(image) + if mode is None: + return image + msg = ( + f"Converting {self.path_info.full_output_name()} from mode " + f"{image.mode} to {mode} for JPEG XL" + ) + logger.debug(msg) + return image.convert(mode) + + +class JxlFromJpeg(ImageHandler): + """Reversible JPEG to JXL transcoder, gated behind its own option.""" + + OUTPUT_FORMAT_STR = JXL_FORMAT_STR + OUTPUT_FILE_FORMAT = JxlLossless.OUTPUT_FILE_FORMAT + INPUT_FILE_FORMATS = frozenset({Jpeg.OUTPUT_FILE_FORMAT}) + SUFFIXES: tuple[str, ...] = (".jxl",) + CONFIG_ENABLED_KEY = "convert_jpeg_to_jxl" + + PIPELINE: tuple[tuple[Tool, ...], ...] = ((JpegXlReconstructTool(),),) + + +# --------------------------------------------------------------------------- +# Plugin descriptor +# --------------------------------------------------------------------------- + +PLUGIN = Plugin( + name="JXL", + handlers=(JxlLossless, JxlFromJpeg), + routes=( + # Native, plus the reverse conversions out of JXL. + Route( + file_format=JxlLossless.OUTPUT_FILE_FORMAT, + native=JxlLossless, + convert=(WebPLossless, Png), + ), + # Lossless still sources. Formats owned by pil_convertible (BMP, + # TIFF, PNM, …) get JXL added to their convert chain there. + Route(file_format=Png.OUTPUT_FILE_FORMAT, convert=(JxlLossless,)), + Route(file_format=Gif.OUTPUT_FILE_FORMAT, convert=(JxlLossless,)), + Route(file_format=WebPLossless.OUTPUT_FILE_FORMAT, convert=(JxlLossless,)), + # The one lossy source picopt will convert, and only on request. + Route(file_format=Jpeg.OUTPUT_FILE_FORMAT, convert=(JxlFromJpeg,)), + ), + convert_targets=(JxlLossless, JxlFromJpeg), + default_enabled=True, +) diff --git a/picopt/plugins/pil_convertible.py b/picopt/plugins/pil_convertible.py index 802d89b8..16612cae 100644 --- a/picopt/plugins/pil_convertible.py +++ b/picopt/plugins/pil_convertible.py @@ -46,6 +46,7 @@ from picopt.plugins.base import Plugin, Route from picopt.plugins.base.format import FileFormat +from picopt.plugins.jxl import JxlLossless from picopt.plugins.png import Png, PngAnimated from picopt.plugins.webp import ( Img2WebPAnimatedLossless, @@ -147,10 +148,11 @@ def _file_format(image_class: type[Image], *, animated: bool) -> FileFormat: # --------------------------------------------------------------------------- # Convert targets for still PIL inputs, in preference order. -# WebPLossless wins because lossless WebP usually beats lossless PNG on -# size for natural images; Png is the fallback when the user has WebP -# disabled. -_STILL_CONVERT_TARGETS = (WebPLossless, Png) +# JxlLossless wins because lossless JPEG XL generally beats both; +# WebPLossless comes next because lossless WebP usually beats lossless PNG +# on size for natural images; Png is the fallback when the user has the +# others disabled. +_STILL_CONVERT_TARGETS = (JxlLossless, WebPLossless, Png) # Convert targets for animated PIL inputs (FLI, multipage TIFF, …). # webpmux is intentionally absent because it can only read existing diff --git a/picopt/walk/detect_format.py b/picopt/walk/detect_format.py index 5931c23c..47721777 100644 --- a/picopt/walk/detect_format.py +++ b/picopt/walk/detect_format.py @@ -14,6 +14,10 @@ * TIFF lossless-vs-lossy depends on the ``compression`` info key; :func:`picopt.plugins.pil_convertible.is_tiff_lossless` owns the codec set. + * JXL is lossless or lossy at the same format string too, and the codec + exposes neither that nor whether the file carries JPEG reconstruction + data; :func:`picopt.pillow.jxl.is_lossless` parses the codestream + header to decide. Every other PIL format string is checked against :func:`registry.lossless_format_strs`. @@ -35,6 +39,8 @@ from picopt import plugins as registry from picopt.exceptions import UnreadableImageError +from picopt.pillow.jxl import JXL_FORMAT_STR as _JXL_FORMAT_STR +from picopt.pillow.jxl import is_lossless as _jxl_is_lossless from picopt.pillow.webp_lossless import is_lossless as _webp_is_lossless from picopt.plugins.base.format import FileFormat from picopt.plugins.pil_convertible import is_tiff_lossless @@ -107,6 +113,9 @@ def _is_lossless( if image_format_str == _WEBP_FORMAT_STR: with path_info.fp_or_buffer() as buffer: return _webp_is_lossless(buffer) + if image_format_str == _JXL_FORMAT_STR: + with path_info.fp_or_buffer() as buffer: + return _jxl_is_lossless(buffer) if image_format_str == _TIFF_FORMAT_STR: return is_tiff_lossless(dict(info)) return image_format_str in registry.lossless_format_strs() diff --git a/pyproject.toml b/pyproject.toml index 6c61a5e8..9a2bbb01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ dependencies = [ "piexif~=1.1", "pikepdf>=10.5.1", "Pillow>=12.0", + "pillow-jxl-plugin~=1.3", "py7zr~=1.0", "python-dateutil~=2.8", "rarfile~=4.0", @@ -31,12 +32,12 @@ dependencies = [ "typing-extensions~=4.13", ] description = "A multi format lossless image optimizer that uses external tools" -keywords = ["cbr", "cbz", "image", "jpg", "png"] +keywords = ["cbr", "cbz", "image", "jpg", "jxl", "png"] readme = "README.md" requires-python = ">=3.10" license = { text = "GPL-3.0-only" } name = "picopt" -version = "6.6.3" +version = "6.7.0" [[project.authors]] name = "AJ Slater" email = "aj@slater.net" diff --git a/tests/test_atomic_replace.py b/tests/test_atomic_replace.py index c066c7aa..cba0be3f 100644 --- a/tests/test_atomic_replace.py +++ b/tests/test_atomic_replace.py @@ -50,6 +50,7 @@ def _make_settings(*, dry_run: bool = False, preserve: bool = False) -> PicoptSe ) return PicoptSettings( bigger=False, + convert_jpeg_to_jxl=False, dry_run=dry_run, fail_fast=False, fail_fast_container=False, diff --git a/tests/test_config_cli.py b/tests/test_config_cli.py index cd949517..479b1b3c 100644 --- a/tests/test_config_cli.py +++ b/tests/test_config_cli.py @@ -11,6 +11,7 @@ from picopt.config import PicoptConfig from picopt.doctor import PicoptDoctor from picopt.plugins.base.tool import Tool, ToolStatus +from picopt.plugins.jxl import JxlFromJpeg, JxlLossless __all__ = () @@ -71,6 +72,27 @@ def test_lowercase_extra_formats_and_convert_to_accepted(self) -> None: assert settings.convert_to is not None assert "WEBP" in settings.convert_to + def test_lowercase_jxl_convert_to_accepted(self) -> None: + settings = _get_settings("-c", "jxl") + assert settings.convert_to is not None + assert "JXL" in settings.convert_to + + +class TestHandlerConfigGate: + """A handler declaring CONFIG_ENABLED_KEY only runs when the flag is on.""" + + def test_jpeg_to_jxl_handler_absent_by_default(self) -> None: + settings = _get_settings("-c", "JXL") + assert JxlFromJpeg.CONFIG_ENABLED_KEY == "convert_jpeg_to_jxl" + assert JxlFromJpeg not in settings.computed.handler_stages + # The unconditional handler alongside it is still available. + assert JxlLossless in settings.computed.handler_stages + + def test_jpeg_to_jxl_handler_present_when_enabled(self) -> None: + settings = _get_settings("-c", "JXL", "--convert-jpeg-to-jxl") + assert settings.convert_jpeg_to_jxl is True + assert JxlFromJpeg in settings.computed.handler_stages + class _FakeTool(Tool): """Probe-only tool with a fixed availability.""" diff --git a/tests/test_files/images/test_jxl_lossless.jxl b/tests/test_files/images/test_jxl_lossless.jxl new file mode 100644 index 0000000000000000000000000000000000000000..58c004b37e10f8fe2303fb443c9d4e3acc0d256e GIT binary patch literal 92889 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�(ec>vM#5L0Oz%s$V?=x4biFBo3G z@K?8&AGl?tV9F_kS4%F~)6Q!PGB<|gZH<%LjP*`E$1-CWl-Hf!G6mRX)?%S?_fn&Le0~f&L(uUb4s_{*Lp9jNQ)XJg78DF zio;bL^6Cu(g!44BFa$*to{Kua>wj5eHdvARvay_QZgCOSHg~vka2|9jrHxgNf0#^v_9FafT3`p%C{GeU*|jef%AbBL06$wpVCh4m}M%Y`y$@kQ}} z)`1p?Z(yHbGz0TFvj7WQUoZ`aozWq|JxrMifn}ND=9Hlf)Z@F#iZSAWQ`Fgl*=@i} z#por^@e1uPU_j7Dbzh>GheUN>1A6Aqic2dC5q`q;R;MyG;h(Wo@7Jx0g=UGms2bUo z=Op>M$W*N(_gjS0O4!cWbrbC#e;vkwXmbT9{D-?(i;4}z!h84c@$4)-|`2xOSK#zXaO)e$RYD<^# z{`n+p^8|vEp;(&r6q)9>gq+^K9A2B3_pio9n$EA&ZH37z5akcUAEyf@Gc^fJ^zGDE z&0#lFC-Sx{6%v1>r>+m%=QWysFRS!V3;&zNzBPsK`r)%cgX*a7MU|#BA0`Q_q2Ccy zWC~%yBhUL8X8Tn>>r24G$X)l>o-JUxh6h^sPB2>0@%+UA?x2Jh+ImJ6aD9&D%xHWf zz-UPLHlno;!_xGl;EFP7T%5-Dn^0k#^-=Otclch22mD!Ole3t*`B}PrIo=*wM=(2F z*$;6%&wz#_-O3elWVt=iSP+c|jvmzGoBuA>xY^bsg<4!j-aQ#mWYtIZ#1V#$_Ho!R z*A7ycK(O}u)PF2RFpC4O5>~rg*5n7~C;9u>$$&c;86SUf^ zX^>5hNFxA3cU`U&k_0R4h)_`pTVwpi zNaf#&2N4qb%%PGjUCUf|hS0t#`z40$`&22b;p=d#HWF9@0qW1g*Ig(xh2vzncLEB8 z&xuznH4#h-rG_EsEe7Xw5r1O#e8lbw*X^dq3s|NV)4Ac&lku{_^zardj59e3d5-1K zXuC7nmg|k!e2iAvn6nmNri$ypLISTON1z${;@!%q7^6iDLT>EiSDZ2l%hNqjNsd`V zHi2Z+ofe$$`VQwht5-WuA^;L3|DYMz{g6Q7uBJ_cx#rlU&sHsa`2}jv+CeTNJ9i}C z9Gdiyk>BpKa3LwmWH$J_OYawM`VAV*ze1GHgzARU7m*V~-ZBKwz^bPHZA`1I-lndn z0vgtKE`1$Zv10QYU(F@c5|;>;Mu{VEHeAl7%#UWK#67zu=Dt1zM<%83!5rSSdGLXx zDD~DmK#M+Q=d-OU?Iz7#9Y&2ah)UdC=Du){Wu%rPZ(g+T1v%2pkIW6X^9=-N6q6CM zC9^?;1$Va4liv1|UpiwsM4X=Bt3Q7_-31PZNwf&m%yhdJukjy`5{ZATBX3dhGq2nM z8yjeO1y|`)flm33#j-BADH2pL!|3r8-nr;!#UJ9frtC+E6AUvtY-voXJLKsGX|!kA zo)r6_m4F+rGi$HIFQjjU*M%tq##AK#407BNOudJA6ZIGm= zB;&_J>}N>PSlzvaoGywpsghE3>L$SKaxjayVSsPt&C}jaPVG zjz<{oH@I}oj5ZiUKrQlRj$uNFV}kn2thBF2XVp#-pL`TcOGc=ndSet)>+(5~IMWgi zJ4zxa#{}1T>D|>wBv0^z<7CEprEmU)mG*eLYPmk;1(Scam;E5%R7Xbho-|f}1^72UGX!S@ zBPTYig+kF%FtCmwQQNR2s)n=-y|cHEyFZK|OM(^+5x_7R`Qi%a8~t4QdErvxdz%Am z44$}8V139*Ql&tCQ&G)UpQ3mYj6TZ>XU|~a*4q$YRcayTB!GwVhVD2Q!fXJu5)UR6 z_HtQborb^VqJY7`t+o(m+>JT6*i0gy(w5m#OU}VEdSs0i!OQ z`X5rQTrrdQzO?rCM{#kal0jJ$Y`_=(tt1*@L$ac~7Zc(ZQuge8x#~aQxcQxyp>nf_ z-XM87El{78UlX~KSSoHo+i61`mOxskUN#9MV3(DX6%DwF+-6;OH6jD+Mfd*qUVlW1 zjnof`ue5&20{kyg*OrTR*ROS@uJQ@K3Z=Lp?KCHyRG|b6N`( zdcx$B0w$KoPE0GFpNNkF!~Nht&5Y%ImJC zEIEiO|76_6wZxdav6YW9Rmju}4oSTREE9$oT-n@i@D`)aMGVJXPR(u3Hb`2(azZp1 zScJYZ^~jeYtNp|C2khCgb|O>W1G|5I_uHe&JyT_ER<6%x*nB<#`9}->uGnC$-!@#A zUR-fJw$h!pP3K@%_&TAWM7_`yVU)fIS_mCLEj|{Fp5+{aFU4oSDW@n;-C1!4$Nkm7 zMiY}c*6tqsJcu$&?%p$(Kn0lE3m)}Q?B9&Z{DzC2Zv{e3J#Kr!fHDmsGk;rEK*l9A zkxo-Fxt0hj#;cWyQ?tWv?0z1RbfrE4Qa@U#xd5aTBN!8(9-E2Cy=~sD8n?Ah*$xH` zlE;l)+P1IyOEH^knwkm5=PQHH&QcY@lkYNd8*(fqDcxRL*;-ll!MemFy_}b3LKs)7 z!}tZi!C^%g>A-!lgQe34?J$r58zQHJWbEFbo-6vRr#Q@1hZ?W-ZamtECF)+T@Kq~^}(HAHn*LhMSY#jTjXa^!b1lC3ns~l>oqpdS6bM@*}T6&U8MVvmWo4>)_ffYkQ|*20sUWfoJNT6Z(sgf73eGlR`QD zx+CSUmjW-S?!^>;g0t^=`OUiX7FpA8U*-%w3uz=9p z*aBiSS`v5Lxxv^=1ufKO0Hv&EI@o{yt2+1h_+?EG*`U3uAP?cbJAA;I-!FeddLf!z zhC-KFG-+~gx;clv2-y#jovz)tlk0?fk7nM8=B;tE3f9hkErd)^M$jSB-}OVnJiBGg zMQm}0V8S)#IE#5aL-A_`)L5Iqvf%vHa<&#$0PQf?8=|59rEGBlsbs~(K)r5;Xe-ps zn~d#C`8XJBwCTpG@MIn2L#LUVXC1=st{yN|y401|RBt{4a>GZf#9Q-%y-0l?qAxcq_XJ+@;EkMh~PI+g5!qvV&j<8bo|>jMGK;2+) z6bbi-B^tYQTv8qWiTv>k8{>zTM{_>A-vs?|p3`<3j{m_))F=mcw z;j^?F48hrCbn^xdcc>!O%1P1ZJ)WI}3C>(<;KSg}1F<-!P+7RgeoP~{4)sWduIM6o zVzHc@%SjBUQeWB&hkC%aQbw0>k09n>rKe95?aLeAO_m$aX><4!tbOadGMcM+g6YiE_5v1;4)eGp%4Z~Ck z3mq)l{7P8~rDz}*(Y3jXU;=B+tI#DLTp9hrmD6!_5G`mFY;10_PSuZY6a$ZB+`42xs40B^Lbbh|Z_pQ=t0Bw%MCmm(5AKHU-8c-|~ zTYIgs2x(zeCGe2>y*s`7Q?7WqSC~;(dunqTjElpV5N_^9lb3V+EA`(rdtv*&p`?DZE9?TR0mh|RQZ^7<#VxP&>aFU-jGv_20#Xz;t`U;1`i>2g!~x>B&}FnE)|Vn}-ELHZ-5yvh_ChEu)_!h( z+xxl`4jDSS-tYMh2`?B7yu(#<*3l~0)lqkW1jvM;GfS7{)YcA_KohXd!vp2+}SH5#7aSOo@o;Qr}_Tk7KZZkmB7@3bx{Ar9Dg!v(j+gN(55yh7d6!347mN8Wvn995WTIla`i$@F9 z9Vcwofx1WQ((?ckS828R+Es-3nd)Jw4klYeB#m{8#t5`O0D%6Lg2VE+E~U;^>0X8u z(PM5nFmPqCO!iV|#!@mj{b?ghY3cT^K?Uy9cTWHwvp`{5~ zjO)fbOpCKB?r4iGOKm`+DAx#8`r|<^Jh;&aAQDQ9ZK|deKM7Ln38KYXc=2Ln$`9Wp z%G;*ljc0wNrIGr<^P&jHm1*ww^k}gL-=N?zUS8zqK`mhT)#b7p0yzaoKNJhl?b*k- zUF*{8R2Kxn&lmg@!ryr%-j7^AX=l24tujDCHlwT2o9Ly_t zA%t||m)zh=G909E5gU&ml{4EQb_s&>kVviTt!>%SO-7Pu2W{ zFrtSr1~M1M_m?({|Dl58?`tBcGeF7FeIC0KzM=r&?$!1qo-uwyLt`$Le78R=3^unpit3K)<5kFErX_ zNBGevrCVB%&slLoP?O9FVDKb8Z5tpH;d*3OQs^u6N+>02m}Rue&GbXY+-5ln z2KQ3!DB@MT*|&NtU?-#vmGItT?e$VCyLY0H?T1ioMBI1DcSK{5et+8C@mc{Kf{Tf? zF3x&qKsipH@te%_sD7rMTGHG;bgfG?MI?p5BiZ-`-yf)c&*|oZNg#BY@nWxoPe`9o zDx%Ir-z@v;b1E`IV$0^unVpj`gRbvBMI1%SU!NDa2~sWi2Rjpngq=MW>M6>Ou1s-g z0LjFl7Ly1ay=c=&aMfD+3=J*IlWI_mZ|gplN<@ZZ0mPwnUxM(oq)jU>58*{;UyP?r(L0kqZcIq!o| zb~75D1M)x4nxdp&DcV$dcI1GVL-naS)goo_RExrVyoXm6?wSuPA(Ik9lOqCOh6DVR zPm_KGf+eGyWsflJb@t=06^6lG9;50AkZ=yp$@$W6Wu(FQQtsVH*@usx6u_C^Vbyia zgg`z(43gq0b*w4M0LOrAFBJ4I(L0=E-XhF<|2IiNR#4!gz!?PwKwscfO@vYV5)0n? zsI_rHV>NrTX(qO-7!*_B6AfqlH6qTzjY@lPB;aW~IM=7=a=CeAE;| zIP8~zG7IRBb+|bXE$SNkvX81`Sx%0SEzMCfjlCq{BT+187ZChI;UG3tJFU^YkM&LW ztKC0pyeO%&w>7f*+bCm3&1g=0roIm;0q~*msM<)CVvOs~&pUi9o~nQZ zpA2a#5s(p`GIcJTmlJqYTgF-3VS-jKx{V1LbEJ%-YX^m1QTms{jvI)FyDG7a zdFLkse*=UkZ573Y3n~tK(PWT-RCYj|6jq7bP7cux*8)nYJM5GkWdp$0!GXmSR_`b}j5`JMrbs#k(4H+gbi0l=d~EgikQ0#9x~#W312O2~|v#cI=+MpZIU z$~0G%nH{1yZX&noLVc}??&cmGHfOo13bYFm;?8SFTB%w8nQdDAHJdopzD;~~tIN;i zJ^N4z0_WhiAg>r(EsPk}wgIMco=8JPv%;TCdFT3;t3WW+ayxQ^RiYx)C7f^uX=GDU z=>9pyLLsEA{6s+zmE6km`3Rn*VU3l)I6{b6IeL?)v(?|{fWzOfq~SVbpQAiC6YMA|EI*$t%@Oaj*5*zfZuFNgcai8G{dmNe zt15W8Zl{gqUzFVgT^j0B73F^6C||F^AEZAmVlHCf);ka767B<)deF;ZG?2cmy)SD8 zE1YEiG@oC(pk+L7#c+Dtb3oj>uCpQ8h0^I!LA1SerbFRUcpS z6d_4zV&l9(s0xU@69H!V1KFP%q0og>yExi-xt{|Pe{c}kD2Gp(oov^+5xcHgE@Xlv z>EhJJx+Q>Be9;GHl}af@^+e#Y8c+J0-D;%Yed{<{!$g{f6NN34r4cO@`{5%6ZgoK; zi*LLgPONV8+(J`n9gO)7N0-+uGp(>>MrjeGAJSk8h|cpP&|*JitZbn%tkMNTJf#_p zFo=%pfsiZ$-|AXmo^kum?+>fq8rRqcOx^?F%)Bk(jW=NLX%( zWvoz6l5~z+JqtnyA2GWTHLO9jzK@@)ULQ8%@^n<-q2=wJAy!A4ZK=D1|4UI}_Uer5Ra`yWzu~fd%&i?;sq=#M17cl|W z{$i0-zX?E|EK=Rp;yM*)pkbyQraXF8+VM#ZiTEIMZp_S?+;J;~by z^^P{Cd=H-4`b6zeB3>P_n2@PS1`p9M)=s;FSp(ZYa>PU;sgy`Yp$zbs6dzrOq|e9j z4E9xis$K%q)a2oVKy7F=su?wiwAk7Gi;sA^@d)OO(emxwG6NBe#t0h?To3hlL!9jg z?l9QsG)O0ebMR$e!*{rI2+)NXIWd!o=0dm(uup2e);CmSPYY_(0m}{GfKehSU z&!s8K*}{#ioadq5jl70!`u5E|?e8YW!P9FKlYi0D{VAs%Gp^b0HVCwjbncAW<;WJ}l;1u$_u_j~l9HzBE@hhyX!YNt_6;{GsX@UZ{4)yD`MFK1$OH&w6R9Ei$CK zt&%^T)hbl4-B^~1BD|30x8Hy`E%^r>vdgeRU(^8&nPm4bh(a(G+6T%pA%YI{*_;B~ z4DJk6q$1<^aQY6D+1h}6Jy6e;;gN}i{&Do&ea5)}@Rrmtiz2f))HQXpG|&H)=07hj zxHB%Hci^;W7$EI663cB3D4syWpx7?zOl%VS_LTot{rq(_=G0B#^YpF~l9HS+u0xjW_DMbq`~!RzvOz22NpWqH*s zWCN52LsvpC(wwP0NODym!Rhwwe-%@ANC8WQt#`ERw}mO2v_vkKH?Oa3vzlm9s+gH9X?b67v zV4CSW;Qgwp-2du&QD!z|4nzC~0SN{16!c~8YiuG9}P-2U( z+5j9R!Xi054_mXjm>hc8@UEc)VJA~-T?55nF2rIrbHQJy+-RdiH3pp>=QX<@9GmQ?8giFK!0KR|IgSf34E`{c)!ajc{bu9$O05;Q;g)dRq#>}zFdg|s)wH2n7;3pv z?P%eWS#T2oB-&UmOfbg>@;8#A1;EyR5>sjBG))9Q0tG(b1Q|W7Tv`-Q3Ko#~q*%AE z0P%|Ju!It{!Cjd7zhG`U<>vmg4p*$@`gq8hF>5fgR>5^kQ?kj`Ac#7zuBMQT*V?|= zk5A-&WeOnK6G|DON&P%>spb=1Gq`@(k~(mQqybQ%Y9!J$$TGCc54ZP#m$h< ze!<2Wt9|^dn?xw2Pa|~`OtGl!B$oy=37v8%HsZR&S}hjJ4qJ8S2tFAtDcpV#YZqca z?^uxD)Aft~C9fW=RFI$G>;?fP?h_nWRi9ssE*X;->6Gd?{-YE=uX6`20pXZ|EIENh z{ZdBEAOCQ#sKKr^C6&#t&b#BnzIbAnR1F3RMWZ^HBydHDSYIV&&Ii|&wtfZocwSd@ zW;Q9g`9CYpAhiCDZreYc;}`LS zH`&ts)I9%As8~_=$92#T0da*<@_QPv=}WHmxwuBt5|W%H0s;3a%oMcL+aZqg|0o`} zkY!b8R06m$>cU|&p^Jn$H3bX{zQD)M7-5^{h0u_^L*Z63X?VVemE6?y<+5-UfTTM4 znWqUzb?*g~kp})t86$zC6R4$BnIoXQGr=OuoW+p)%Jhr61Eh}!CqmvNWSJ^+$lUcHVB%opU6K@IOB1y;SWTswi?wI1_CbprpfWd&Z_ic z!ej|4Px)0&SS}-M;7QHNwzf?U%3!uKxKMSa)L;FZc)GxOhF#%v4|M=dRT-Auc;-Y0 zt(ECU(R_6i46{OG23_*YLUt*b86`j$E$~7pra87hRKNrHsk_Kq49vxO_5sXrK~13! z&8IdFH<>{@V@)I!H)^+V)#_rdPbcg_>_q<6v+L15c^VegJUy?N{eGF~px3X`Hd-Fv z!Y;MbR1yW-*Qb_N3!hseVr@hN$ zfK*zuqXPcvU}N3C_;>oe==jyBJB(wwolR75sYu{9=FE=FeY8D{4^waQ-Y1Q{FdU>M z9_@IUgx+asKxImKDHOi=?JOJ>4tSvs$Md8Kj_nP!p>{!igq^(&v}8%0ZC5OxRRVA; z0g|33g+EK{chE^WXX7qCE%&202wBDWW|up|L`eH@O-U*C*3hgt5mkE;oKBV@BW{+)8;)Wm{Rv z^Cb)0W7z5)?nLJQdoJag2BmecRg!S6A9mUf>Is&$j7;i|_E?ZIFqu<;Q)Qf`#=UYV zgziLz03iDp z4-*JzYbLKk`+k^p4D1RV_>IZ|Y$G?!Gnb=S$$`YnDm0ve`8i*n$;+P8baq|{sL(|E z(t`Tod6;w_*_Qh7<@z9Qx%%9Wl7u8lzWklMoHKU8O%UqB?K~`@zq%G z&(a5cEEkha%?%@N-*0;T>07eTcLEZt+=N;*7!W|wfIBaMCnbINPy`5nrnsnRYcEtAIkx)C z=iK&G>anY1D#N8`KS(cf*Q4u{594hcXwT$Wp|@ldU22K6 zjaF8)Lr5tG(7@swN(KGhttM2RO~V2u{+A%u>4l{KvDwsCg$>`V+n(pa^fyw?2qa4x z>}Msyf}5{!2#IQ8!Ef71EStuGj=p^3!Zo1!GP=sRU)6<~^ z1u3Hp1twb>y~y7rZtTnV;zVw&&{Z>Gu<+Qj$arq;*1Mq#30iQB8|ci=A4gT2HGlEd>lC(5o^%cG#&&$}IYa zb3V+J9awU^+5nH*W@avWqKuf@#o4#RRIvdx_A$ViL$ZA~kN5kDS@~(|)81FsL1+SG zOl^f8+%Sc3Bd8O!Gi@BRO}d7v6gQ3P>WEk(DX@Kmph%@TNTZZKy_E;*4czPbD;H4hz?R|p*?kGaRows=4xk#sm63X0wT#euBS~sdK1e9S5Ltk#Jf56p?HrSNBT#pT zLT)eI6B@KZdSN5}&|k-wv)4UuQ~?;d)=-}Jrvx~~v&s(-g?fe_Z3#J=zyPD|_f*|} z%^^e#@OL4V9~wuY9Ay0&pZZtv86|TP+|?5GR-HgILtygJPG8{YlWU3&sje|{r&pm6 zm1S|s3K;W#GtlL3*wwBG+vOSKgQQ&RS3sDW=2UtjlF1bnX-OgOWU*v$5Bix|LZ1$_b8alFCn zIic!HeH3@mJ!0f+NY{9X2GpLbD)6?RD}_5xIkS#^mf2>2|JRh&(wk*i=^b`SlvSfw zyve#!F!mYlS34`zXKD)#xb&-5d##1fKLgm)o8C_2j@k%Dqvy#tA3bX%)2+&XZqrh3 z)$dE(lHnU`8})htW5ILMHoPq`77$7IpWU_jxi9Q)1z2V2mQ8A7vODbN&O?x-y8Jtj z!vWhpM&(<9ArnOzY-EE}9PgU)1Q!l@(G+^NpKDW)NKHwJYRSE@{rX^!rCOD@4XWa2 zR5TwT@6~)@2*^p&PDhFpmn4*SK~ftg4Id2UvbxWDzdNxufv9OB#2~Co$x?V+6E$|6JQ=THh#nmo{ zJHs>;%Th}8__KdOf4fkF`$mGahF_3(x_d*X%7`cHIjn6@-W3^O^N8^nsYKwQZH3X0qfg%v?KMF5}m0&A(|h|c~hyiLP*g(PdH z-c{y<5K9Nmp9~tIs4Q&Y;*`= z3l9$LdLPuH_UISJQYvV{ovD}rYSx1L9_gR4J>Y6{=0u|b%=bW<&#ibVYK$i(^eba3 zt4Ng=BuuIF)Qt4tXL_Z7x=z~`z>`DrJ zD+K`cj2!{~)G?wd@pp*dOnYG`+DKMZ=r~S$pUwN54rRN{%&1p4ND9ZmxM!YAEt>*D zYOA=h6cg!}&yOe-xW>N9E6p;GA z%CCvV@VCr=2H-3&QszXx83`=_cSbnAF4_n6cM+qq-ix1JAapDny$|=z%vd8Hm9Uwk z)B{$7cnx0jS_(Nc0_ttZp!RqjLi^mM^ex|#cMRT=fBd>j)?M*XNVsz84d59=i%NMy zyH_n#;3}Nkr1YG6Bv0E1It6bxL%k&l%GYz7R+bxchEG0&nG3QTDd`v+DLz1g#`J2f zqo^T?N&Eev-*1Qg(2mdFGr@eWzx&WyN{kzzAUMbmMS|jVMYgsyT|mu0H^%bYgxc+k zj<=>>l%dM{*1hvAAlZ7yns$#?Rt9hAMVqQ&nG|+^XG?ftrPr2{8sWsZ>4q2p9F`xv zZ)IZc;tLU52O?xMYKr1Imp2ep&DjTE=co$<6H$TGYqjlkn5Jl_14^I)3vhS66^F_r zg8w2~=9H-dDyfBcjz^aEnUzy+DIMT{DqQ^|WNZlhRK>qv#uHAGz^o#8=~m?CT^qQ` z%n1*o0=#(LjtQa^AFVmjKXxf1gFn~Hk^jDy2t}U1!Nk<+#pwZ|k74JZc(M}!LjGn2 zrwYTB95?E*cbS(h!nk31`A5cZf?KK0tT$ZCQ~#QJ5XCw&q`8gEH!khvYkrvDdHCcNH;-=a&<2Eum+j1 zgZHDENBI1auL(-7Q%L!7!hR3hE41@xgk_d))s6~T4Hml64Ii{QMB9`;ZpxDsICkd-B=kMS=<9>jerhYcSR{ zmhh#tJ9$4ib8Iy9v@J5F4a+&2GoUffjb!sYSBU2gdg7x)127 zGaW;rhlukTrv&{Zbx$p1(n!s^ME0;#2&Q9WD10? z3~m=3PC~MC?tD_6jH*lW66L($Q=$+|2Prp5)zc8s<{&IajU)0uQ#*{?LvQ3HaDBzQ z5&^pvpR~FW&nfAr&}=S!(59FDd#GzXFc|OxcchZRxf{1Z1N`CGu$zs7DyR;WU6ruRZ_^Hy+~mK4EQ~VokB(CXszcb%qaa)!U?t&SAUn zB81Jzr;AVAXkSAhW22U??_N{d5*|#j_omlEZiRAQFk-j z?Ylp?s0#GFPFIu&%55C?@!)DYMDuzerP)@c6hb=3)G_01!L`zv7(ZZgHc2#;3Rj{R z_>1e~?_q5DH6+anVe32B6{o~Gn(Mlj@f{UdNv^yFb$L%cFpHQkFBQ%?An}NUa$aPJ zB})$vJu{=Fwkm|~XyheUu0D#$j2LZwYWR_nzYWkh_YlO(cSM4N<|U+Q)+JdTQlh#W zLEjL7tbUc3y(t9@+9LxU;^5hJyA6xz!|gvNxmh+`$tc-%ZCMU0R+FCJj&hgPHpgst z{si3m&_w|IGMqCg!wn#ju)ZaMQO+9$>j(3#t(%rQucMQOHY8_yy0Zg8ERwF~Q?=Cs-(Fx$4E z0opgvSSTEc`a7OOjQ)>fjZ@J^NFFth14eRW0GdrXG$x*tv?q%Dto=u%(#cla>DiWe zA>!_G8qP1>1j~4=c4Y0q%T&^FNdbv!E|}CD-BAgN1%VT6tYs8$i#wN>1l{5 zV2E$Ml!ueV8--t5OhB7uDNvn6%n*HAAhMX^bgNHUFxUZSnqErJ(YV^t9Jd#JqQfh; z3)~%Dq^T$|OU~eioSSCZ8Ydahh1MvBKps({Y@@pC-@jLm4$;_KZWbSoUPeX7hQ-zv zi)KZ+tZ9J>{&ABYJ8w|J!#2iIAJ*dykjI-=xEA3z8n!<-OrRIp>kA%AyXbDx92=# z-qP2o&?`9+Oo-uD45!(uAA!RT=^^PHbjF~c>$|^0u>1i$nfC1h0Ij-LA3Fm zXvw5pOEM5+t~eP5Y98S*O=$(U%+G^T;7{Ww69OG?iEIWe_IQ=awbBMu27CqREltR#_*zyl0+jcSx#zMEQG z+nzCLkzPXdBoXBrXx)?>ByI98y|u$Ui z17?@UFhJ@OaJzWYUm^M`Re#24oSYitEV}%Hwql^7228Cyd?G##wbql8KsH|EsX!n%VoT>1Zy$0CL){(qb6{n!h!f(8hnc11j%(nA}%WH0%crTU4H< z_Usm1($oT#pCq6qM}4_RcJ;D-(2^Sr502L*ETUsg2T8E2q|JLBL_cOp0w=;xFiEI8 z2_hhQG2|X{hhygtEG0!7v%PWzQbm_K0dr4A7;fQr`y|efa{kvQb)bB5C(49X8STs`>{D)ULVsIyi#OlE@iZTx;E6_-xc*f8s2ZTLA=|ne( z#zC;`AJvnu_E zkMq(X>xE_9t}(mWj9?x|FIF*L2d6;~Y=`J6tb{@vx5lJ52uE)Wze-L>x#8v1zoIKg zyT?f2yU9AZupclglB4ZO>YKze-_hfNH1u`|Xn?BfH$F!!23S~sqInR+d(Cq{f8c@{ z5u~`Y>x_=y)A&qtGsLRv@<~6xaYic=3T+Wb&Hw%Yu%qRwQq^-vg!_USYTgK`M(OB6 z&68L9jO@kaD0oK~L)awo%!;_Or$JN+5;boR;67h_bS!phBi(+Z*nbbAHr6XgpykL% z8pJ;)mO|vn?`@)BI~jbkF?!C*;oSru$AU*?T7h3g^GJAI+Pd})G7_^ERhBT^nSjZu zTR#pmwng?TeC+Z^Jk+OdV^L7yP(1OpPfRMxG*|wPF?L*)N|%g1S*Sf4VpspUbIAng zS}1Lkib)x)6plLA%gyewK5RHu5;wFM7bdhXa6a*Wn zaM~f)xrDPA=^8^tzA2o*+MTpyA#5v9Z5&xjQL$DE8Edoa9uOAiCl@YLcb~DS7(p|V zkeWlL2B7GG`|#5YStEr|Z#onL!svk%tUQwG1bCcI;UF4zM5%)#>O_^KQhBJKSTdUY z)5%YH!S`PN21SOxk_tSUo)9PM=+q2jK8^6Qy4=rF5->C5Kmp~jYRSH6^534QGc%ZxwKU}>HzQtu4w z=TmJ`stsY#y0C@cYP%Kz<h)K^lr1m z#;IwaPO&H)5ieKtZ3Gs3lA&(19^0c~8rNrvG=^$iRF4ERLbfsjAs8)3yr3jgNf^;JO)y*RvLB9l2 zubtdG*m{z96{!te@@KcH216+JhT*t?!Na!G3sV@MLgt*j4vhC-q5Fa~1m+UM4fTI8 z&sQ%k!`9MKUzCKut7IZQRZc4UM8 zC)p&h@t=xiNwXE&;#fJB|8U43m7?Zd|060u`P65g%)vj!1!^uA4dWDt%6v*w4hCvz zBhF4j0*3qCRB#2q3f0;GuBF5z#dwt27bww|d47WY>rSyatp;sxi+*KT+^V>>rE>o#^Gjabvj`!-5w)V;Q9Kw*Xb z^Rr(Dp*-a4^t~{Tim7Q=#konR^5vqlA_Sn?&e<* zIs%k4Afxb0F4ImE{DNma^*~}OV^KEwk523;PzgBx4n%OC#_HhV|M)%Wcl`_OO)c*- ziqEK%HW|c*U!q7)I%H6M?tG!gfbX zTHTquRB$Ijlgo5K=yY(v>JPK*>X|SYIr=3*ipVOF;k3>2G_r% zF#-a9q6NsLfU8JOG5Hj$ueBtu7GrLq6Q!W@5Z*tuklTog;gM__4}q$= zsB;|tl81tqygt_Ac$~IJ4O%jMms+f)vAE=xY-$Xd$Xl6FnO|1Yhj%jKGqO3%>8t;7 zp{Av_HGHLME9LJg(3S}Ljxs0u_|AV!A4JLe=4kj#HDcE-I^4&Y^;0antFu(cw~MCC zf1Z7dm`>AIdJstDU^-RT2(n7{Zkg0iE{YPJmF3rJ;3MxyK?C3E9xWw*S&eXPWJK{{ zcqVg6c;|n1V_An`$sAuL6CDAM|HEAULeB1yATQs~xjeO4a~N2GhUMS|u#WntGJB5_ zbicHFWAK*UDV^-Oi)78jh&Z7{?nh4KSAW0w1H<}Odh{LjiI^}OVsuSomNX@Sg|xObVXO_ z%5x%*LYFpNo)CoEa-=FO&L5Ca1gwyOoHs&pod^Vv_VPckjBAK#0uvWuIRCWfox^LI zpODR!y?_l=2G*zH8l3=$#%-XlR?;%ys}T5LDXZ1L{%C0QW4i*G5@}ulG?dd8YR?47azdr(b@4OAF-h%u^jDQM=pJGQ<-M7joY&BvuTr3J_ot`}< zqtFrs+7W2Qh~1*njJ+-*#36+UauPCBpZCv)r%-Cf7;rLd;dCzNTDkYPBm-y@R9@ZA zZM33mQ`R-G<&%rBEC>wYr5*dhnm_H;;*N`iVcg{~;)pZ&Z)r2M+=1Ru-Y9NNO8%ZMnJoQxqduJ;;%8}I_ru&|)5|d+xcjQgXz-?!w z6m0*|ej1_KQW`CKvgK0|qZIA=Ip6GQASCfos%5Mm#Fj;}$P#)8fZlf&+aHOXD`uaF zS>|yqe%m-}KOpMTMG3|C4Rk6BoCRmndIR#OS^pAbVz1ks>(DwtKt6m^Y4^3XtTap2OE2bxA=&-RFfoaH)0%AW~%yL*47R!KDz z#C?kEh@ki3p>w1$(`bxLz7>kbV7pDiny#d91^>J$L8uCE01ZOGj#Nt`OgAAm&>ye% z@GXCG3J^shGU^2zqEL%G)XKhdT~ovV4`dAw^#rGk4u;3k!%%(pE76EEb0n>#C!b87 z9Rt_u@7|@FS0n}at*kH&R#yr{v*DeyB=^!@X`s4vJCyY0h=-zR3Wp{I<-DTOW9t(_ zBfds)AKX)~ojhWn)j-7`VMxIZ>H90Zj(u|I5l*0OzsHHzU1Fr4I(z_u!{8dT+WV zHHA4eEu8FM!8)m0*AVdPU&({+%DWc(oTCxA(C836DA_s6 zcDti2#J?1*LbL`0 ztfS2|aH<2{hzcGm(lv?*2k!VrZjW|vdG2TD;CNHnp%zekzAxLEB&xDvYWXBPpoF#_ z3idkp0Fy{2d1r}hMudP9LRF@RtqOC9#2km_Z{}rOb5kYP2{Sg$zdT6e!mxgvw`%$t z7uFIp;E;fVCF|iWWnEO>4Ob`}Qh(%8(7)a)MR2o$*W&uRT{Z$^WqVQt!fMGlF!fEW zH|L+H$G*~6tjA&V`V-X%$dDQJ>kb#BezVYt1JymK8u|jr5&D=F37!U}w` zX>>Zd2X7;2HnpBNXCM{?`PAne+?t7Pmg*+jA~0a;Pt(I_0hs!Y*g=@>U|PR$uPtfBcT2kqRo& zpL~_51Ad+Rl$8~uO6CcG8JnMuPM`0ap(GyEE%yhFQtDUgvPwIWM)9fxCDI*%@JKWR zV~U(2rrLYpCfX2lGpsDRm4AuL`vJDlMoQE=D!AQj8zXU^sEQWNS(99tjt4}15SG?( zWDULI@3M**^w_L|q1Sl3#H{iQo-e#VXZ*;Sm58y06haJT1_FTsJt@ub(?}fA6(A(9 z%4tb>Fy3{~W@s-7N`(LSJg)zH79q3a6hK;4uNrpE$(`(w5vD^ha7V!MH~6GTRAJNiD@$jI7Cb`GX^-_h2h7Dh@xhbqq~?97nG z61E8tQY`ZXN{!!~{QISYeI&u_!oe69Efc7>q?lcISF(iv)M3;|-4n08HE}xCZ?~{5 z2lAVA4)>=Ttq1G;t>C7pZeGd@Wz1h4Yld3#o2e?PH!)abO&dQ{FjY%`sBl8=z@$D` z>aD-gkt$mom%WCPyUc#V$G-I23bhUj4RnOQlzckiHUf#P=v;!sNe zMeO`JEXxkOq$|(V`&R!?V`OVDn)I5u1GN^ZW5Ip?3TDM&{e+l=J_W3Cm3Ey^^D-O` zZBd>09y!798Pol;JR``0Jda(_-xk^ z{k1_nuj1%Jc%(%QAtqPi0_a)Qt-U#M`)k!gzKk8F{vwaump%M*Odf(+dxN18T}T5V zTZM{o<|2C!E-=Los)F!k=R(N0k!ZfqBYe2gP^x9l?6|mHHsX*y9%?}@SX$58N5s4x>F^cdHUcZjsWY3yx6RqrUY`Pc4zN#s|J=T+! ztl>+kobT=jn=u4Opxb?$F?8u84z2#v8oj#Ks7>)`_I3p& zYTQUJ=JkaU_q-m9xd=hOxMPJdWwir81X4$;CveVxZ*31@|Fan|8f#H@>b$H|AJ|w9U&WBD}*GN;=t=Sl(0Da=~O996`S5TLDw#!9v+5>-?7_Tl`TO?#c$^}tWTeg7uo0|X-s!EQ{u=${^rR% zL1}@S_0rxUeIXa|yxQPLL+oL!Zq=XOvx*s#7$A)I(Jx|495Eb4_F8AKKe^DE-qepK zGHZ=rmZgY3JpV3R&0`FdY<{K(Jb_=;<;^&eQBFmKgpd=g!V=F~>8zxP?F?3qUJJ;K z1yF2t>!Hbp!|{!CK~cu(+2v#YQo#IP8KC87DA5)0^_BBZFwO;IFzZ?;brTJIbVL1V6$g!!Ihr8~ zzWoTf=eQ2>5zIQDQa+w{VQU(6cB7Fd;!kbx7PMv=91|a1I72&Kp=_ z3I5<7On7@X0A$gMEH>1y#!=mq5>sjLDN(%U=(}oI3tZ!;Ji^5BleRG3NA|*zeSjct zJw|(JY0u-&YnXr;1c3;`IYnGG90KEcH`h8kVbgVh3gG5`9wfw?me{YOTQ#=C7Yv)c z-0SBCjzl6lZ70E?8t0qFUb8M_A$RDrbeZD{*B^l?%F;gvBkhh0VBWbjGi|OBp@31; zlHV+Cq(dJJ0!HDzFZc&Xz{tsmV!Le)S7xI=n6iVi0yO(fy+Mn0+!b#@yM@2&T{R8< zbM*@NWb>BeUh;eb4?^;b1@5T#UD5A-;(f7mf1Kp0Ug1`tjes_ur1%@nVGo>9G%mi( z8nd7%)x_RqKO|P+UtT3h}uqEp@Zp4cFq$XOnV*j+6}yGw#L(?koj3UgH2!M zoyD~GFsbHqH7E=|vkA9UygKbjwd`Cgc?jG(YrBfH)7b1YGSvP+V#)q8Gi}tkpR3NI zjux&e2=%-nfb&AZPD$HDo3> zO0`hOw1}uyf>(6glvxrj#Ec0m|AI=~#TeE|+nGj5>Hpfy?Rtu#I=;To{*E+~Ogd)3 zP!MbO2zRMzAv)F6Qc(Zwh7snC!&_ag_|mUYQ}=U=k}%QlEkdm|6tq65DBBF#NIPa+ z7%J`4QW?+*CZ3$4?#R+ZG)cTLb&R2^_FppC~T8`fW@(EkAYU0>;_7*vzJupq#j%i+HV~Bc>0MGq~ zc|RiA%T^CKsPiH1`CddLAbOT;`Z^aV=GpocpQ*qw{Of7AdgI9$)PH^vg;OF!(QirE zXjAz8@QrW(I{-`qCYY10HV~4Zg=47|PJzeWv6OLyVW)`F>^KZ+6aPY4^Y_jJp4Vdd zI!Fl}59~$V$Gg;Uoxv7o5SYL2OO;iK zYUT8_OOym6gi?S#lX06NC;?dSr@)d6WuB&tvgFSOqefUX8<0Tu+UZi(m8MjjhvV(p z-g(o;lYH`J;FDb&-z9qu&I}dS`7A?!qY!$D@P8;9;11R!;$pd!@P>i*xqzKFsLN`GIAH@VA!@nVNI0$djB zx6kzkKcy99%v4b}-Ta1JL?oidiW;ts!N_^{o0h>Q&cOPmGBwN!Zw_~Q8tGmP3rM`B zEI)$79#JBc)T;Op8%h885s*V8-;xTE&v)VjET`Hcj3hzk+eqgP^5=)D8=Y^)-YRuF z#&)jH#bjehOrPP-__!Af`~wG8XJG#2K# z46(8!0-~_mS*oltg7k}&t{IpdO?17DlxKY?p%R1}`r2@?s_yv2t0ZbT;B;v`T32(_ zMr8H4)`d0r!jBSo#VdMUSGL!YNBj+6Gs!ak{~tN9kUrRB&T&ncxrce6t?}3N3ApBs z1q7u2Nhp}QMlUZ6vS4_b1aCQ*SJuh^*~ zRc|#?vUzyIA7g#;B1JX4*EmoU9^8TrQlI|=>LMh=3ZFw4KuZz#GjJMrD5gTZR5*?T ze9Jmsk~dhnE28&j*n>~z8BEs8e1@!NwraX$lXGUyX!TtF`ltya|5JoO*&l+1#zJiw za13;--;`bl!#1IPhc2lEnqyQg_sG%Q&j6nIt)+?7GxGCvH7#;#I$jG+=o~1+K-nxF z^)sYw+Ntc?ypzykvTz#I#1`VNgy$W81RbP&Bg6()w?>$tb)!hCy%67hah6Z+7m|6i zJJt@ski>h#VT`ij_Hqu5p6132NKIMk2`#Y=(OfiYyBQ@X8dkTe1gm{JekUu=o9$i#s`&YYbecvrmnW3y)x39z%;`YM^R>UU@U#i1L5A=NYD%Ik>7~J+i zs@<%ni0M9f4NhEMB@|_?!W z_~f(-bE+@I%ipb&T}yU-N|i}5f#0PU5)0pOK$DOLvU3mQt2vPty5ki2HmJtK*oh7pF)?HsJHlagahW zAW78Wb5U&il7$KD0+1yFxxd(+K^n@A~J~!&+eh}aLca@p4RG}|!$=O7B?*ttf z(aC!&IKURxFrUsb(zK17#g*im9AcI5S<;InL92Y#fo6B#-iWBw;tKFLa5BGuDp6JuLQ)ZEoS>3{{tV#8r@v#{YpJuuX<;=2xe*CVWdWOq^aO7wXW=3NgW z-kL*^2{#eozEonRUn0?OEpfH93~`b{W_>KamDR9n0}J|)2m{@$c#w;NFb2Iwosw;I zYm<9}PrLP&g0%rx2}sa%D!_jcnu^)=N`_-7d>&OFOs(>-A~Ho|P_ZI9Z;Lo1lcp0= zFSS0#y@U5+#6|)jIhpu_rWZP**1B{h+S{X1-rX~UX*wvS)}ME8CnaY5$Kk+aAciQX zky^BMM58t1?SLF}<@O&jF|1ax&ApF9rug=1{0C;%6}kYB!Nm#n^IEb=E+i8zb(Ozc zCTX;u`WGLpdFWex~&@BgzV=ah6=ZMm) z6=Us1t8>iLcBIeP=ME>KPxa&=D^z7drr`RdsTpL3U)*J{t@Bwy@n^JVY=)pqZ$-;z zm=%L?0=Ou^&XhSgHp&kzzL4%O<<(z1O`b0BVp3=1hXWlPq6sa0%83iMd5D`6OPGy( z-E$^RBE|*DnDFdDU}kN4*TBi9n3(NNcV&MT#@#nPu13BF7=KM|EO~qsTC!0J0*kBA zZb@@Z(c7(8X@ZxRj9RRLA>Vqtp^fY`v0F66rqfSX);j?Ir@Ks)4HNA@|o+Kgx&91^se_J_e-B<15c=9+*K*Ss6@f+9pAE%RQ{ukKnv zz0&%XpOt1xB8fZz!0t|>T2E1H?Pn}IJ*cvHs!l}QDVCIEPGq8SW^v%+O)I#SnZq#; z1m;qolB&)%`hX|04y7h^J}p@~{Xi5Y*6}LuLZfMF8$~l&z)Dtp;>ezB<65GB+OhzT zT<3|(reel*P@jZ|5}>a+ohld-zSro*Wz?2I69<25boXTgx43?hCWEb0iv!jLQsRfC z{)hEJ<0p#2jp_6;T53y&;QI*#SI%52U0U<`%BwK*_qdFIYpLfFY6(Yh%DjErM#SWV z#1vdNQA_cI3;H0Mr0PO%PplAFt%bZ1N zV1VP_sL;g4W#)@UW?xxBw{GhdQg3fDT?Ys__Co zc+J&Sy9bW(@QI6B+^*F>y)O4vS7neMnoJ!n+Od7Gcx0eLtP>M%ugR+f(>~(fs27cR zSh841b17Re&RPyvq03A_NcxZUoFwWGms%B4zeXREr2#eyu7Yi;MFa)0bamh$*2Y%B z)a&)py4eDeI)CyUINbc_J5@<{Jx7?09{oC)vNRt#3*1oC+cfHsA{?J>vKK7p=SQ|l zEd1$nt=gRcP*1EaL#M{d^p;qE8Sras=^;382%`)<}qy09VSxz%)#Xh`FbJac}F9;+?$}0)hwM zTaoP$1YJBo7wHIXV0@|mVPCIr2A54wtq)4A5+zB^3E9e4Ui-H+tk8~I!^yHB`c8=X zYeOqsmXqdJ8O_MEcsD8qjKwtIb}u4W-O!il}l|H#&CThrtwzBM*-KnM#Lm+l_1!*wXf5GE|eOas!C zAM-B$2sSkNC8^GOlluy?SpW$?4a)(cVD{i%OzaHon~EFtD}N_yu0MtQKdq#ZC}%P; zlH@|{T#%L0M&54yVF)1*M659XWlND|cX*t$WnRbfc1S#b$^VB5`?cQtaTY^FA zvB8S{+hWgg!}!vtf1InE8sNW}T7g~;h@6q%0ehC8FS+PV8@cQzvkMcOmAkrHxoOhQ z-2u2IVg>YMke|li?F?Qj1-{=s_4;5vO);=MU>g##Z2brmJHsDPUVvzp2%MLW6$J0` z(N<0=msE2>+L!hjO^-_Q>HolSexTCxJ%B4LSU<}HzTmqZ%q`xTgjclzau8A-+Xtio zmd3)(YCD8O{J(lct)y=AWx{qsfGNZ#xBha2B21g`{g^R*%k##X=)H0}g{0UAMr{=+ z?FhX_VWl!>_i<=FaFPKJ9XFsU+~#t0ttkoZh98ux_Q`lhm>Iif)gC86j-@$oSu5R( zFnuh^@3shfezZcRJ>df)q!eo?d09m;Ww;Wql(Ch6_na!|0tToo< zNV2R%wEB2B@haMEFfac@Lg(=0VBZF+uL_F$P%iUKaPDSs9&Qfv3Jt(soU5Zx3OQ=^ zreD<-p2`>w{%XTFhZwVdvQt$FGPDw1=srCW%+&Tct~d+?gBP^;orx_^2Yu#zlOsy+ zSKm%D6-z~yf+1!$S|xG?;-vpJY*XY2It zU3?KE)?nAq$$BgYI8(ROGvj|1DkTL-ilpvQYrP5=M^J;}?mtv&Rap4K2->ccyo6lH zMb6^BEI*;rlkFaG7mBYl69eo4i=xKzpzezIz?+ksr%lsR^Z=Ah+f|ca!KLKxH@j6h z7qw{#kS+Q%Q=TYYn2mWIsA!AO6G2eGi?ZSk)VheFF=fKFukt;+V)B>$QiaMZlsXCU z>sb3V3Je&;i0LhH_^#l>9D-$+A$J%)f3_X6bB9bDdXjk!;Nh z?7@v18dS-}sx1h1HY+UaIGhS&OipApd0ir-s_yB7Kt#8cfo_y)ASutO@R&CDAa8J8 ztO(G{X=Bu#O_pV~7i(OU6{Z%*k^j+F;9th;;U!lMnHQsA^@T$OdTx2Y!;dI*0W#HP zGMPqD%UsL!gznrse2zw%BXVDi{nmp`k-s@@vwG(V+ zqw{wmF9m>V3Oz$On^@c7;a{+eby#V`cFjOkOumA!AON=sHn1?%XjT z48uE7f@>P5$L|i?29&L=Pz09JO9FMe0l3I zAw*+2m(VD;I>a;r799oFA(*9O{71zsnUqB^R1aceYtOlzi~%1TtVK~|`iY=s6k#$X z7Edyo3i-lVP=d97VgowU*L@AM0>^k6@iF)#@aWGl zus;zT*lpRM_Hz7Jjw7l(*`Slyb%G;!OHs2(ffQ!e_3lu{g_*STo=!hIIsD&M{=0dP zarvao`j=4QY)Ny>4f)`;izIGbqq6a+T(w;OiE@^1%Lv^rTZzh{gLwo**VIgCFDsNL z1ObPwH2@pOCs+aM8u!%LDPjRid-=w)Eoi=w+i+-MqsW-tUNfBA0OTzwD9mVdVtp$* zA@8B5GsrVGtqnq!Fv-?2+yY9iG6YlV;LC{ntTS=MYH8hSe0cov3K8}TBKiO{Lm~NIVUc+{*;JZGVAz1=kKt0bI8oYcAa*k$%4kY25$O#=K9jx<^~t7~4QJrBG8)^l*xnbr>?H2TkVjnV8o)>&?t!?EvQ11wIpL*jAAU zuzj9ryU4alBrO}BQh~+@v^DR&;`;j*as_7R8l(a#IBA1H9W2-`ye-KB@R}UZ+V-tY z*(a=CDWMAS=iFxoGP(DHx{Drg{;CZ411TLqBirAZvwWd3oy7ArU_%-f3wYXMn#B%f zNVFd`Fw`);;h7yhULy$@8mOrC=^_QG0W~%=@J!u8X3X`$0My~O9`BAn0+IN8xm->4rUB``H(B_#^wLH3{lF9B>4b+$J{cdhAqmYXS6m4Qw>ozaY z*OxeVF{<1UIp*Fea!;`bd)F&xoIB<(&@7F-7%6L`jze;~1b~HUhcq(9dyqv2m^FRui9-7*=pvXk|p9%+B zi4fk*w{w2UXvJce#5C|_71qu_iIe#Lk1@+WQ_jOXc~a9pQqhwq) zP3hh{ljyFrthgm)gDv12GqIGdl)7TF@ulfoBtQix=o z*K&??#u7U`=Zr~Rx)F`tbSG1-?M5+8*)(E~Q~MouhfCK5|A_@CuCs&y78`C7{!XI$ zG2YOH3zviO`U|_X_u9=9=UT@OoBzF1<~FDwDTXY9f-X{c+S53C?;E5qM+#2?>NbSi zW#h@8@W9@r{0*DvX_$XLVN$>}X-D0j@fb~9O+j1bQ*kD(tCh_%;los_7nYl8Q(2(tY|WR!n~WmZn~5d! zUXl9plhyp?*^SoXl9y{p=dcgk@yikTPbm`01bra_YOS60cxJ@Z2pjt&wCN|U{ z_!xk3#$G6{eu>K_^Cgqzd~Rk+FUxPrh)ivvE~RDiO>P>jSNvtel(%KaRjI7KAgcuh znR$J@yQuT<{CZ5_;(>&x=)lViv^7Mjkp79X69ao9)i3e)S&HxZ7%H0*M~X4xzJz2# zp~ZRLf-5rtPWaTHHzaAQlGncmTaLjFyumZToaayXdtedb6A>hb;WNJHwBQ}af`ImV z0kv$llAT5nNlo8Zk+K%s8-XTLZNubmcm(nG`Tr#2^U4{=-kV8F5|$;koq;Zk=q!+1 zgM3D!>u$dx6V0R!FCcBsS#^(SwZOPNzV@fFOVj==SY0$GmDeo4W-#hX&(^{C7|9aY z10LQufXLp_iNvmcpaCq$9Mc1%lgppu>&i(0VUe=xoAbwPB)zK{BB_}vHd$oa%ntggc^wuT!E_Ym#8HWC*XzC zdo+S7B`p^uT?(Rk5u}N!wKCSbyb_Sja#7hSq#gaaSku53Z5{;01M2L?()GY%a!`&aqgaewTBE`FWGI&@Ru76I?im z?!?>T&)RMmHWWJ5p-%r2rsC+2@3Fik!;&C97Tj5P6QiKsi4O;c@ zYBY>M50f1PYU3{bZXF;gh#xSZ(D4jG|3)C@cozR4MSE>dGs^HTrf5+j^s?o(!+-7ffn2X2M>!%+Dob@FC`l#;QkuJzkrrUt*`1wUu;yk3a zA7JZ&4ms68g1I1LZz6G8Zmb|Hs9P3`sr@p)6BG{nf~hy8?Z$dV`B6!>>FDHv?j_5@4YM2s>%PPz!7j|HfeErY?E;PKIHJnCxPrSC zSQ@;IPnyyhpSrE`%xi{2!@$Y5xNyOEdQD&Z;WvK%F#kp8jJ!;SFYTI02TMx6QOfm% z!lD~VdvkROGpUxrf?QI$ms4nLrWmTm__xeG1jB&^-D>L;Ac_8`Lu6&4=H z7qqq$_%5a+$MQJ-gZax<4;GwVN;1ZmD3f`lzq0#cQ{WAR)AhUDiQL20&YjHB*yOIy z>l(EP$j0Dwpie@Of}rtdM^I+V3k(p5pmXU_pj16|D=8V2sMr3E6nn_y;z>L~du1~% zYx_rPV>|n#%>l+h_jQ@&2wby#9uDT0F7Yab02fGvYZuZ06hdYy0>*#bk zjaV3=+=UXOHY(|ezwq6E(G6klWNH3nR2D_ut>hXQP|J!TIfW!imlak)5j+QDzg#Am zK8tUkVY$AxsUn_hIQ^?N7>FI4f!EVdY125Z;3YdIMKQ25*VI$O(0@t04ud^r^LngR z#e3j{WhWu!SFvI>x53y{ZKgY0n>P0A#u4J7EA7vQB?eOgsqPfzEL3}BwoR(X6=(8b zGS$raaVni9l{{+?myud4ZEM4Agrmw4AHEF1`n&iS%LN~EhN z3x4j@LFQ6!1}0VJI%aY>5RGOFYS3w$ELy+ru#}RIa{4b`oH&0Y@OXC4k!fJF<}pOA zp(+WVzj9!H0yS8eg4Jkby5}a%MMjk`72l40k?ilo-n9p%sJ4&(-T|e=;~@bbBZ_0F zSNdMz6WV~`$)#+%`tg^NbqWq>3zo#yN+M)BD{MLJ2KPDUI_mcpamV#uisQ_8s{E}^ zs>a(dd6gVblxeuxUhR+s5q4E5J-f?H!*WfLB1j+*_#4&x>A%h-+yuql#!to?TgG+n-^a(ir;k=v@TW`z{$bmNIkkNSWd` z0&EmG<$Prs(&>Oy7|@ztxWZ17Q~p2>|8*KVIAn`(?yNmw0ürq{`{<)`+LHu8J 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

e|ibfjYx{mUn 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�Xb+H$LgBg&#`xur7=s`phmiyy%S`3wk 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 At^fc4 literal 0 HcmV?d00001 diff --git a/tests/test_files/images/test_jxl_lossy.jxl b/tests/test_files/images/test_jxl_lossy.jxl new file mode 100644 index 0000000000000000000000000000000000000000..b273388d5f0f48d3009ce9bec9cfcae8ea5fc9e7 GIT binary patch literal 77911 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ⅈO?om|dn_iG+VElO_DI=v6>ng0G$V03 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 z zCA)UAe0_N-XRXBdlS_hev2VL{q69T`$lQu&AZk9cX!IV#o-3qvt?>CPZb~ z-LxGqHjw)>7%q=Rd!ObQw(wuOdI8zq8fpQT`vueogtK-^sTau1z;1k?dHZx+YYvb$ z`R$=hCN3)CQULtmaOB>_&Ebi?Dv4(}B%&(URHi0Jfz$UE!0nDqfaqothS;^U{c~eT ztHcd4LGpf|R$~RQnRRx{J%~2GK z*TiO9(xY98j+oO)hWOArz`K}?=>MY%)vf`Q6j5s`N|r-A#3!-uQvJ<&J}OF*vSKOW z96!7KnQxN zAPM(((z~bw@5|kNIQfa0;%mg+C{p+gPOWXUj*|bKks@vFv-fN4CipoQe4Oc$@3)cU zx4jo%}+GsI!6C};k+c z{?xB&S2l=IEjAzeTHJ|R6=Ir~JNcNm4RA!pvc^US3pj+wzmKe}t5gm$hIH%wl7M`Q zuhH9byO4dV)_tJ1__&TM3A}-~+{$0DJ%XmZy+uY}_dRW|lcXQWRt_-{1RoPSQV_e5 z!9=e1E0#ejGR+O*1bY>)1~4VyX}K&#WlU2jeF-9ePGLh-n99gJhQ6RzC#m-n4~7up z*m|0?KUifc0w{4LXSwEb?q5LbJ_l++qD&4|~<*`>qD(5}_#;*r_R=-XgK_ zq6R@gR~;7_LpZn5)9k|MCG*b<*&l^$#ieT#sG>fT6UP`B+0J|Vx5^&9nzI+*!3Uw- zsA?&)RsRvmS?+XcUN!83jfJm)-{v`7UP(G2a~2r$kiOBImqmDquOUKqe^sTEm_XRA zbF`!_0k6d%7b-1K$N7RsxOc((xM%O9Eewjv;5S%X7wi8gmJ<6L)(lmkJgzJvPLRE6 zMAZg3>rpqYyB<+44{i(kX;g+29k|p}t0}VyDGQ?SeXQ!V|KzdI%0r&Z_Hs|B<<{)j zufA*M|INf#qFDB|M#1j_Czw2mdhw$IUKlx!{{%%Xe<)F=_+p>zk88MZh`TBD ztYYKrg&b5Q8Z00WSVhy4NQwd5ffH$%nNlHU4h2py6K>1qW?EU}YVxYtCcI`*Dn4P> zTR?k|#%e8Trf7LW&EBA1aL5c=ULo6$NL2fJb(}VAcV$R(rgVUN(-?r1RBSD*gliaC zkGb7Rrd(d3Mwx-dqNPz%vVE&g{Bi5mx3e*6L}B|Dtl$OU|@qIH%bV529uq zm;Kxs%zc5+qqRY~dX``g5m=!0M*XMK&cx00sdONhy70~v^4(gD{&id!wh>qYOM|wZ z7J*WkRX5>!?-sq~P3TM2Eg^nLfq~G2Hf;k2G97tpFJl6ED00^9xqfy;sVcB>azCEJ z9(VmdjwG~;!8YMhia?66wYln^4-aZ5As&u#!okZj&&r^iZp?U|HX@1zrN)bwyq$bf z@WKd>Mz<@W)`mtY*VqiGO~m)(9QjPxh&wdW?(^m4FMgjyYrnYrZ$qPH>*dl5nnb9; zQ7m*EN*|9(sFWb$!hBoNx)@?66d< zDLmcS;;j@1F;`GJ(L&KBWdEnrzDi>@mI)8DuzYb->^n8eqx$~1xM$)=TXMbVn{c9a z5i)=Q{vo!!Mh9h4v2C`*5#Z+COf>lCGIwn~vKhSNC4-r`uu(6?)dXH`R9!Ov{3Jt+ zT9ZH*Pb4m0y4n|aepN)$`uPC@61pzq#(ghxDaB!*cPgMzbTq4#3kCek6=f&ZWE?;s zX<3&3_AA)9j4_eREYiNaWjXJR+<{%wn&R{%s}SWgg5I#OkU$j`iH+ddb$|z4{&7Fc z5x#B&cJ|r;j^1;7dpFa1lM9tuB@;()6)N1nv<=!*JTt?jgClZ2it4W4KHpJxVhky_nW8NVi=I zCMETM2+=+gGBF19EK`h=&n!F0f96vd<96t_kmRD`n8trO3e1e#?st#z7ZJVyZ+OJ> z8ufJ)j8B-T%ofNdDukd20b-W>B&c$+m)q4La0?X{^6`te_n-58rfynDjq{TU-x?~M z#?%V1pnqN-Oo+<&ep>U2xLYL&?o18oh@L(7MaVDf2kVgWTWw2b!eh7Tl+dIqVwm?F z2v&yy;ej|$yp?=L1-~m{L7tDcJz#J`^WZ-)`3Y;tm^UUHB?a^t<3a}8!CA%y@@A^DD1NJN@puWbT^O!;T#Zu^)Z+QE$(Ncl zTma>4KV}sMwIdoj^$pXaYF=-BX1J44EdlOD8E~Fcr>w_Jtas6@sVBSd3$jy6K*At2 z!^lj85GC_(T+~b^-y>DDNH8yad+^cUvV-Sk!-}3*xcXgvwKOQ8#_TsMg;L8G4gxZ@-_8+53i2~^H0GoI*R z!LU;NWo@lYL}8p2xrhjs)?1I@vf*Q4c<6EM84Walv99SHLs>bl2YWN6jlUMvNNUAJm^ksZCM?= zyZV#2c23ATpFI{dfc}1!n*9UO8RD`5DnS1uLTbAolmtpJnk`}v&8%c3?;P)C09>AB zC`PxJWkkOF$!4`zy)!L{R1P!v;>ChCoSxrsb}KCJ39O`HDA=C|Yg-awBm~WDkJUw}UzLdvulR z)Pim>G&aF$n?#Owcv$gmmI8{&L^0aHqRU9bN24G0#JSBxOnKc z1(b^dh1wP^4=ol<4Z}*DX$>Ks};TF2x~>1T`cz>T}F;@pMAi0@`QjuJ*X6 zx~3^sTwg9jz>hAnTocgPeE?eO&orR12lZNPpu`L~oEFDsV#dT%K&(q6G?QL}usWM- zh+Xb2Y0AA2ctHFOY<;lsU?X!mXky4yK!!jf^beL;0p>IrGiZ|Nag$MK;2Sthq5i!N zarQGZxNPJXvCr;$T??l&xy&Xsn_cm7q%Y44+m>V!boIKw%aO3;%54z506Cie#Pkd* zwwh=T#9mN|KLo+Nn*nC_z<|;vhq6pU2R}HB-#800V;#udzNB9g)zbu|?ewm@sOu)# zE_sU5u|RM*SU0Z+yA*XeCT$^5hsvk8IFtDrn|!-63-X zeyL=H$M^|wv#zy1+t`MCOdZSXb(FwkvDOJuCW=*?y6f2Y)TqN-uAf)PMqh2#sy`E0 z;38xmyqLMsH?p~Pq=cvGtbX8L>~zffRn^&AzQ5AAC+aX=M*mBg3*^XV-TrZX!^ri* zNzMTk6=9RoC>khDkA9evZCa!-|4V(&gYubLj0*vQJ3tSG%D1TlGJ<5Yi;Ta&NK zS|foZY!{Iru|dF)C|7LIPOX6}tDCO^2nnpCg3)l381S_S=W}ig8}CjOZy7oBg;LhI zRaJ#TdT;CD1UbBkPQ)))o`7lIxb5YC=ay6__Yl*p&!PA9+4W@9-TMo(*|ELv?NO!` z7HLz{tC#hP=aW{jP+vJUa`Val=m>5z=xO)^KY1k4Ld^WdaR^@Wb;~pRb4s==k3y<~ zs>YN9Esn$$eeH+9+h7AhZz)iNmMs}icJ&3}72gqc)G3vMg%!k411|v@HgNHM7%hkr zLD+_~@bb4F$ITknnxm8mZp%g=aDb@avv07X>!OKy5t9R8O{jwGSVPHJcZ48yW`iLtkqc6+j%YwoNGldh?Mz)87G)4ZLqRPg3K5b) zg9NmuY}c!T_jg4cId}QR+q<^2N+jW9SDSBtAA$x`Yg4TN$ZJns8DOUVC8=>?NotP> z3%#3Wg^ZN3l+ssgusuE^DznEuvZ;e-M;K z{9tc@qhKw*}LYIX(E2I;BGwBo`pvl(33cpfm4ljVN$>#{*OdjN9|kW~xI0 zmXdOAYeih5ZMChJ#V#CO$(hF<{V1tinU9wQ@3b^N9;BY?d+M_+j8vaaIZ$#dpou<5HQSGw*B~_E+~v}Jmd@s8hY&wuRp;f`EAMlkg<0I( z#T@|NKwy1h2R@t!9-2SSBdTe@ z8D*7q=%~jxc#+^`ek~e1ARcDiY)`p<_cFeBVD-rb&T-9EXatWXNA!WeQkg~lvlmfr8*?(XZo&H({AiGK zE~d5fk%q4)b6VPXddv}YUTd5y5 zv?)p}sOb9z>g|g$YQlR4Tvrc;c+!5!RGa};p1LZQh{FjAsRW}aDEBXbHpm=kuw6(n zW}%FHes?!6n&V`mifoh64x#U-$t$3McoC{ZKR9-*~GHuVFDZ}& z;ku0B3dU}Z?{iwd&Z2WGqhYrEMCev`c4Tb_eQ=c)z}Bp-Q|~&L`i$*Oo7udCx2tJZ zF=oEWxCC;fmcJ*W2^aTF>!%n@B{rLoHy#6?mlpPtm~GzQE>soM2&GZv7wPEl3^$_K zV3Yo|aVCxN3gPKmc{X!-8W&yphi(2MN7v7CQ+O{I1tWg96HTE<36M8f#yy3Kbl*+K zY8uOfWo5&-v!e2T5M^7TUl&YQQH(5!(Y4DfWqHa*7*)6-R*?X6XX}d-UcR4Is?RYI zF^>UNh{n!qMqVma0{B6TFqIE|a9FH5uN);_nvd72j;az{hzu9M|F55!L>CN*>mE+; zai(Wo_eB0m2Ig$hC|um_4akv0PF7uDiK18CH5`P;(P#P@8`PR(Y;6UIO= zLDR>qdH*qlB^4;w(D7UvVmeRgyGkTJ0#}5?ZrqNCs3d%l{)Y70kT2>^;CmqdQI+oC zLXdtZ;jeT756$%zi>(M9z_dqiX@CAP=FCJ(ejNensBzhvjTlf!NTRZ#1{@C~O|`az$W%BTFg^XSHm0{P+xOd^aD*WbXw)sGXW;O9u2Y#s zY3JEXr7=!t&I4`}p=Z_Cc6*|dC$8v&xWy$Ld|%1YK0mEjSOQHazKMp07_VjKX#3KV zWI!$+_l3mJ_X03lu}t=mYqB4Bh8p*q)!Ql`cZ#q!4<476Ya5!r(`dRpmw5u4?;jmA zb1`S{<5EsH6JXU(UjD;Lq9K;Yzlz~mqU{8&8Cq~1hBbD@%@q%kj2l-oRw8ecF5Xz z2;B~0p+hmNJhQXne$}>ShEc48E!v?|*=CD{I9}P6m5BKU5a*7Zd{quX(IU^BV>=^W z%hccj&qo_lz$il6DQ9RM6*x+#f8cT|k_rJUIu!BdBpMyT^QebV=28F@2a?nJaUxf3GgOvBdO4L*{*Ft4-J)#+v# z?NIY30ZuJZI;a}Nm1Sx>s7T2a4&#%m`2&jquK=2&DmL}AC`-i4SU?H5WQqmh9egY4 z^cikOr+sd2svgD^V)+95jem~ku;xgjk9k)!0L8P>bE)6BfHrgmwJoLu6qNoJO5J4G zs*PR1VQ@i1DPdAxSM{PTS%}$G9B} zgb$9I)Iwh<*L4j`ITw@wXr?=}&C1q=>RLCKKN=I7oP*lVyxh);9b$GevK?HL;&DC% zTdqPm8N_G6ySwve{9uP6Pl$(zg_sS@rocU{CkIbgb@%`Z3+6JtLluuQ6^zaF@t_o; z5~|I*H~fpEkq-Y_hzyH-*Uk-e)&H>FU)AoajG-io^J4v=@#2?ZvwKOr1i`TP>*$_{ z7r`PRa(9%B9!q|AMX~3U6C+dy&GCf4etdXgj?TyNIge8wGqDqeM;}Z4E3sz}6TwZP z9IZ9&shR;Vrkk)45QFIxGBiaJ28{SZgBdYLch>nwmx&DSAl{|0%Fg0jy&WC;aFYC6OK1dyK} z(|@H#8J9&sD8Oeq0ylznNBeF2LAu3fB5kc?cx2YM5^HE4p(QlW{?>vNfh8D=bG+e% z1G;#kQpZ=&0;6)RP|>)l-h3a_GP_oaLaZV773>*?z@9Shqj2P)=~b!{ams9K)95nv zSt3B1Inho&)CdX{0vQ(Cy1i<{o?kJE(G_P&>u<{f1yaI!EcyvoMn9UIPYV@&)?C2= zil#)b!o2Q1*k4<^`i(eUp{vv%3d)cWCEoYcDKS_C92DWRMe6ZJsfOb?W1pduza|u9 z=qT$5tuZ%_EW$BJ>xhwb+_$>oMC=(!O~IzOaw(cw4X$VMp^i;`a7YXq7ff4UGKF62 zo|N=J@PBJpx_MRH!(ig7Pp^J6S@UL<*-ZA$c-uEZX!*DZpJ_CNoKaRY=EB`Df*!Mcok|JD#ZPM(Dx6Tl+%XUTmL+?!)Vp)IRj(pGvN&*8;RCIY_ZnBH$opOwiXzGE1$}f#iFYm(aJM4=i{gt%24-%hJF$azDGlt{xuC7= zCh?)hzmPYcbD;u=>O^PHaV;6ID)Amze7 zhER5EsM83mvT=Lg(k@pS9&_3rs(El0qOXD?oa+Z$&SL1FzJu)+!$O3LMh0!sCsHPA z%mt|7NZvyor=l5|w{AyXe)~U3&E)Q_-dU(M_V_Cd_CYxa@2d?Pz>b2gmM|+H3CM$^ z=uA>2y@$sXlRC9D9wWv=v-?mLuAdy`E50n8lt%Uh&R#T2$eeEW315TJkAByg^+^=f z=KJ-}=o7a}5ph~_1mkqgr&L4kjAHOQ5-|_dWka&^7kQ{e%T^sHtk)4}$-#}YZdhdH zl{7EB&@+0Vkz4CmA618(<4?(3ykky3@s=4LJwrh7JQ5%!759Xy3u1P&eLHdHj;~)8 ziJluiX7-zmy>+@F#QCI-p>Bc2gL}O@i~9!S_2cVLVZ>1KziCjX#u!HJ5Q2Yoe#!JM zVCK-pX?D_lIx>~@Ds}mJc4Y2lqIzJgTw>=6`wx0*u~;nR}%nfqP6F4!^OzuO3i=~3vnqfkfa1qf)=qgc$} zTr$8?$a`SVlsDQHb&?@-J-++4o_-+7Camm4Ie_d2nqK?-8w`hVJT{%z(fF33r1mo! z1icftplI1DgEAvCL|WEZl9&U-c|UTE81sQ3TBy71`la>E0jfs?(~SP2pw;wVAqLx= z5x5hPf%Q2GFtq>T@)ABCQ##D_f%JJrw3iueF1m);G^6d*)uL?Aaj*Ay01-cG|M*tQ zL2(-ZxnPal4se|k^ZdoAgF6aT8k+~XsF`pnyB zwbd`8rBbSA2@@=+%^RV#fGWI{{~&5WLu2~ZDG^aHd$I2F5gd(disk9 z7xywaKjZRwl_I#8NK7qOHWdJ4yMHB7RoYxVzVq`JZePy#_QJZ-IuG0Wv<`!%L#N?F z-=gtPuVaV6%w{s&v+c@wfyH;GRjIyIlVZRKeogDa2ps2@fOZ{7&{n*Z3958Aj?H!{ zdT%)-=3fo51d}t4)Ev`dSLYLtJQ#Q4h$`8ErU4S1oTieYLISK)>hQ-fI5?&dSzw<% zHvx^mmvlY+z4FliH5af2z`v1Q zoMFAphL|~X_1{#<0E-J}Far+x`tcrBw?NM|2mAKzg(*?g`7Sf~dPZ<%Gke?zh*TJ6 z=*M5Ti;^Y8>9{KNg{M(xZUv)-hm#nR58T`U4MdCjMOcVpX?&_)Jhkw+hRgLB0=yvg z{^NjO9<-DmIIjSOfL>PQyPsO(032GEc3RU2+S0jt@nvhK2S9IG=YFI4;`pIDmI$E` zJlB`(vg}L`AEHS24w16pK|q`sa-{K9`{ zA3Xa-lCoz;NQH*Ng8za94AA} zqiRMgaiZATi%fGWJYx6oktzJQ6iF#1()a_$pYYEH#D&-WpY!2M;2#?v#Q_YxphK$4 zI)*^?KXbAv!vni0A@l|yg0e@{b_OLuUG(f~V#7YF&-G@>irDkG*V$yEr11PdC)rb` zv%~!ZdKjYM$09LO8*_8M3yF{G`{N9wIR*%Ipx=mO%vKlNEzjk4{DuQ2*eVT=JYfYb zRv>ItrZam2z<3N$(4+NHX!HWROYnJj>GMUs`<1jr45FIZ9{ z_+>J^A@T7;fLegbKmnIp+*i4KAw1w$chk-pQS-0EJzkcUxw2EVj{{m8GuBMz_E*6vwn4XuwX;~>skR;rP1n0~9VZXt#WK5br)+E(C{Hvx` zrZCtZr#$3cHQS+iNM;upvA7~F;>HQFJCZ$2qTF}j=Z~kLuja+|IgPZ#5`Pd1omNUW zG`Kd-H9xe_bbH5W1);TqbDzYw78g)p`1faA1t5;L!If6NJ@;3jrp6V1R^-+>&MkT7^X+mny30?3-K({2RE z|F{qMy-b@xzcUzY$6nsP@;w%-spu`8e)jXG`Fv}ik5t!$&haHK<~x#K>i-_7LbLNh zP!BWfyjoW~b?uYY5ja~!e)~Za1qEq5QmyeDaLUwlz@XkXhuXz z!MK(SqI4%en9!%45{EZDDk_MNCm;Y4v=X{NPvN*t+{TE~3x)=I>Qbg&8-Is_PnrL5 z1HWYJ`9%udD(nA!dkU{Amd+c4826%wEp|WRSWsx064?I&+bF5ubr}%{3c;qa&B~Gg zFzJ!l1w&=ubuC(^8rc7*7wN1~xdD&SLk!`t3-ccs!xA-;-Iw`+1WVFJhMNM1^?#Gk zNb;;$bhQteUY2tGU%7`jVB3J{pqjKShwN?Si~ozmp&d$&78Kv?o`%g!jTvm<4gv6v zGK&4!dldU5fl7l+ZVey*BT@diQtS^}bw4Ru=`f&uk`Jz;>qI8l!?^p+@=IO2PlXWk z04xwszqf3~TTxIoQ~6+4*~|hIBVl4`)@L^;FJt9K3lcf(U^Lv8U`0^`<54>1{~XQ( zUYwv*&v*2BUvdCC4DJ^$z7X3B|D3RWo@p=7)^76#d*g>P1GBm$cvnKuGy0Ov;G;$T zJFVCl_z!k=Y*|Z1++d6)nbpCo0_I;4A0KI@QaF}6lmSWkOGOyrfOk1(u=?NL z*??h?watWB{D)bcjtlsbt}^KB(VL>Xn4*V|>I~%h)WBC~NxQRqJb`Zl;|h>D+^sBh z$A=>OHs^Ihh!{6xZR3Zi&pi6G=tOvWu$G-k4wxv$%ijZV(INhNa}0iRF@_5eY=O#p(tru29ezy5QCTUQ~JD@wy* zOa04xJOec30c7=^|CCKOe}l9{Lu_|iAs1i}ZWiNc%*lVyI%l9kR>NHw8X~c)&nXrb z2nyqaVj<+xb{!Ij9CQh#uJb%tXzYrhEiL+34B&`Iu?CYC`<6T*z$FEzDXakGi>Rb|{032lxJxNu zzHr-36=#=F5SYeAl>z%YkjU+nXa1|#^D=LdmI!@J{n;T$vm#_JJkROq$4^NH z(VhV~$tSHNSW8qK26NG@<_&Y*y%^Qx_mQ0PRm|q&-#ZGx`>ez8UJCT6erUA&ghvdv-m*c zjK0u;+f88m4ORE0okVI!K$qlkfVayXXZ=)b3UkK6)ftY%7K#bJMpScXeOkbPmaOBJt8DZ{dfqb#Yw4^J9L6aZUy%nf1S2qTvL-Dv}3vWK!!en zPec-j!>I>!zE96-?CqV53Y9*BWwcI3F@v1p+&-bA`vcA^{4-!bR(Q4D`BKN`@;n6q z+{UA<_r-S0_KD6~(}MDnNGhs(Ljd*OVo{T2_i#F}u7Lq%PrEHWBXnh_*dmWL!mQMl zUYW;eiXzAChBlj82o0k^la>P6I2On~j4|xjhZpj(DZTgr#Pkf=rzGbx-?D7LP)|Mk z{S>IJTxHgGEJMn;O>0B2yv0qH>O1V>|4!flU#f5j)V*k^rLU8MN{fL(!qIi^%= zOSH+H1l$_B$w)xkQ6#lOc=+~FrJ$(($Irgv1Xg6)^i09VbUkp3gA2UL#oki)*e-7@I_L8EMQUV6h| z&F-2K6p_uqA2u*Np`NDHN{Ir$OoRx3e~bBdiAiTUQW?oVAVsQ@8wPz}ss5Kw_k2Nn z^f^sB_@&-tgy3RDV&1_d0`ugk{c}X%=0*JDWp7|5#~l8(1vXC=BCI{-52%AIkk3Nj zr7N7^+`F{O5A>foDlH`_BlwaPKewhIh;{mXKqYSoP%qHvQS@?Li@Y zxjWO1<=gA8D6LZ@=GZa5O9Ph?j*A~sM+HFdtHOOLuwA-8zZO2LDcT8s15kR$bD4TO zs8b^-@$z2uQTra zA(ec+5qC0`hjK{dvPG2((a*1!LP9i%`px3O2WhPIv!7aNR;&ZRxKA}#Ls}d8d`-B( zYY|plp_Vkb6Ipw5=!2q)V2)E8yY@Bu1jmpi*mTXRU$6b*c5M(-C(n8dHR@sSD4j)d zV+hj+gB*dFL^gW8cqPqUpAC6ZUJaHlcYu%5s+u9dh+7pZBMj{r{W8!LV_bI1E+=SC z@f-MZM@;$!kByIZ;QJIMz-f>8`n2`E4fyt7`E9=uslA zr2*RIpMnwzR$FvBhGsQc3#eZCy8z8>yM;x_yBw>gK#nl@=qG27>K+-X6U~*`dndX7?q~7a z!GYjNp#3BU1Bcm6sjznA5&GwaMwG}M zATe5ueYpM1k1=QTu0c*^M>hn=KfT+BAv4LEfj@ z3FAj(EZ&FM_@ZKl>Ox<+&i1OqT^Gz}A%GtarhM$k-g&r}wtn9eout<<*9@L~U8^ zk{#$0Xp;5s3bK&&P21xG-yAx7F$5tVj(a1@xRV~?i)*Ri)cuSS$cwHzMsZ&{oRn_q z;Xx!X)}VNtC|F^dw>rM`=LX13ff6ORmLdu(*40Q!)2$PRn39Dai`koFM6#pE^AzoL zMA(6X!d`E1gH}hIl;4}Uv`Cs@jNvTpv1uo|0&HqG>9;>OhGpuAXjLi^Q1jBP#>$*M z9TD`RM4sRO`XJSRm`UcP3NZs<4uTEZPmr&S+0~7TRFYqnaB{?{-tr&4TVdt77a@@& zyU52tr1P+rJJ@4NkOKv_eB{=%v|K{0a*V{VQs-{r^5eT7wh3$Q*M6|LfPw@g5Zdlz z2!}v0DPyHXPpTyTWE&+f`&I;5#0|RD3^=Q2c5}O96PWZ(mG~f}h#v$*;np5#RHCs_ z8B_L%r&E#J-zg84^8^B}LKkIP{yi8x{)O+0>Qy{Qr-U@r; z^!m^mCDT(lkcbPR1ubqs4{N&tjZCG;w(_v^E7O8no98Fb90PKmfRvJ|4fJ8W1gA)l zU=@X=?+O9rjw=G#F7l2AE|ksK2NDTLW;RXDKoT1fYN;KeR8^~C)@T*RW@DQd|530; z3LY$1ODrUIkHp}dSt`hUyYQSlB{C;5CEI6E~9u`X9&K9FJj3L|3jVXSEMy4wQp`@9{uh3 z>m(-XYUdNcnaTmE0htP)4~7`P%L!XwvgNs%V3b*ry2${EA0@eW^7T1S`zp^^u8f@S zoWl<1Mic8^UqLD7QSJi0fyuar-m9)wEg^**9BgXrIGEK9K{}gakWv<4#sh9hYmw20Ud|RW%mcOOfY@-; zv8HwrKE%4nN78=|85rgQocEgj7Bgvq4CEch`KN!KqqbW+pxG^pAc_?v{Kn*%Y!~yU zdgtUNKVB5ok-vE8fnwuJrszPG(BYw^lRLo(kIt}YGIpX|)gyXMW669R4n8=B6E9fI zR~pTF*2=S2w=0In3@+okZs6dUOO4u%pb{GF)I~YEizJs9*eH~aDTM2cNqx^sJuo3j zi!H)~sqc%J;88=pZzjbomO@SF!Y+@p?+ja7Sqs2=KXt%HpvgE_pL$4(#iu$N`O(h6Lbq051swsz-;ykuxWU?sOeH}vF!J{i zY~jW)+|NTfOr1e64^5HsH9|nim+QuS2j0{-i8pQwzga!wM%OH^Q!2irVsp;YniM!k zBr8G$uJ(N-Tg7t{Liy$&6U2P zf>a6my{L}qmiasrdd;!p%+q1Js@i9xuX4*s<&&~A!`#oqQhE}+;}KA$^{62VSOPM# zt0k8S&5_MSN+IUg*p+16B~$)Q9%J@#FQ~Qru=s6=`k)nx+T70dh3*7%?{e%R6N8kB z^2KGMFT4>_YBUfKwg0_Y1O(c<#bty4wYtJH1>fW0o$1SbU`(f7r8m+#`+LL2$=eXp zypvT75m-a5;#XcX8h(@{z2@WeKW}Al1+(?=cH#h+@re6|>Wi237*3l5m{7_HdyLW$ z&0@}BshJA_W0@%MUGn*cLeHB}(Kwn`aoy%lDf51WA-m#c&B($oc`B(U1%{G^3wV!c zF0FNj7H+J@pXZ1&0?dNtx4tK!92;LT2>nLuXBZ7j(g=zp&#ejQRik$dIbxO~PPY_o zS_Ed2;^JSGNd#QF%O+6Y*PnN|s+iBUsy-)?e40IO>4Wd2*}C0i<%~=cgA5&)*pc&G zPFr!C5EN$pF0q$SEw@n(fJdtP>#|9idfj0dug{WON8ajqVs60b{Sm4YSw!fl14Qi_ zI>udtJ&n6H_r%C|uq})V!qB`!v-NqfsK?o6;MZ;rCcQ! zB5SgXDbZTDRN&FRUww`n#i$?LguDQZ#>}lrPzJWvoRX71&2zFbZ^_HczFHY9f%B_+ zq_W9jc9W!=y>Or7zYa-Wyil{L^Y%f}_w=2ic&icPvjC!=%lhUyIlNy9F-~N|>f{t$ zGbRl3L$_#8P^c^aVzT*?kGu;a4n!gljv31@|NsVc{lRQ3pHWf-P-*u!7}; zKcYJ3whoa3z}l$gGw)JC=ap|#65F7U5n%(1JE_s!XP5IP)wW8Xl~7>JcK;3i z`$M^*6T<19TUH2cUbCVuiLBx7lbdT?Z5+IGz7MIw;$Fv@kt7_LTYO5z=$Wi5>6$g* z&|+bL$FBf;&^l06W%EQ(rSXb?>SE%hXOkOB!_N;w@#9hmNV_|Le6KJYXvOK9;&G{! z3f-MMYhLy>cK-T)B}0RgDwTon4x)uXD&a+rszgMLdcoKfrQ%+}es4PN2=D zs!oY&EA1R$X4d;l$X^pfX47@Jwu3iwc`Tldi3dC8U>@?q^5f48b`mRJKHb+HFR;Kt zj|QG(P5-nepxxF8(Y5vB3=*;Hj3vE9hRU?w-u;Y?l z73^I5%N#ym90sB_&z`wYyH!=X?-0}mCLy<8?bj;*EU2u~0)wZgEVfiY(1v-t^V!F(@kTV&F792>aUevegO8E*D5pz)}WNK^=^uFsP z)CY8@ols|l8qBmT=DmW6C|rbN-u<6TEU*_%-4TPk$rP0ihxLO$K~f^(n1bjc`es4G z$9$xm7u;G>xlVk!+e1&QT___h7SAQ{w3d|9A-fw@(D?|xa@~8aN+)AVWvh@@65P^c zVLBr@R9f4ugYh~$sFyVY!3A%her}B-CBr!!K`r(I-e;+T^C=kzL=?IE0|0q!*6zpA zeMjahJ?5DXrI;=(93y(7b!p4WDIcytAHN&OA33@MBo)N9DC9%ock`tif0ZhbM3QNn zyDzkRA~a88J`4SPoy1(m!S3dnX7}G{;xZGE)4L>BIgmE7RL>be1x~0-v5mqV+jLmc zqfW~f>iJ>Ra63CbM5v2hHA=RhGZqbvP1sF^>gxtRb4{+Cx<`bWKti`U?>GmFUt%WD z1*>@QI?I6Ph(6Roeb{3*SS-!r$1?Q zCb(M|z?BZH^h^6l5A%5(+-cHNL-5RgQTCWF;pro}bXnBeXO#j9?Wziw5Uh-c6~SO!-~{&H%(DC zcV{mop3Q|SF12#SkjP((IL3YE9P9GMB~4mXS(q;Ve!nVA*I^X^-nv{txY{he59MUT z3oL;| z=q-|yiP+PcKlcLb;58@$&WMqmtjpnbv7J&eJ{cSCS{!SmQ*l=uQv{iFvzZGytgOR} zJw`OHxZfG2i(W#X2-QN3`^npUrrz6n`@As=`Ty;8xC|0r#?2u_8$X;F#kI=q#J;Ll z8cV#Zf1uYCj$yGFnnRX_9*IFpRu~nZ6*+FW5O5>o=_u1%()m4sE)ur@UpcdLSjMy zdsp3&8wrVoYQo6@r+~0UzdGkQXn;^J{6ulrg968DuM`&QY`d-}ba@MGtJ*^@vsRto zkitXjDNs;=VVRg9g(nwj%Yc13=P&5a+p~YmivrIDiXPj>K*c{Hqxd@c0THjKix95K zX)u;1S8%{UKHrOvMRlM;X|ECOxb))3)M^0k0W@z^Gk?c= z#z4A`h}5Ey06Ew(S)8fCov-LmcKP{5pUt@a{x|k#zihtvL>hOz=7e72 zMB6ON^tn>0@LZtp3+zIlj`MUF;m%NzWFD4Yz@!Y-$AR%xOt&;zHpgZa8c3Waakw$T z;^&JV=gLpZjZ;JaBe?kziY{Kd8o-UK|^>~RKH zm$)wl^xH>fxL`)RC*UeRZPAC`aRV#2(4G?(?MGO{vfVP}3`mKUpG0Cnf@=p9Y-S3<%70aq4LSB1`OzOU5DdsuEJPeyAopSB%Qx(H8b-sRvSu z;ZAUYOS8#Dt*Kk-`=i=t3N;T2RGtzuY+z9jMGRp$@Q{t}k)i#Z7Fr3Viekyo!+8c5 zuQsimmTIeC*r3QQMNXX0+lYBdWJXG8Ey=!ZE!H)h4G>82@+}_Kz76F;lbv;HBjR@V zB=A|MV`w{8N%YX&yzP+Flqn)g>_LmtTUJ+urOycxT-bLNSq4)0cO0ug&vW+tW}(wg zkUyi{48GfLQ^O?Rki$O^G*t3uvGl7aV<+U210bUUfdahnjpx}f@#CvX3+_{})oLh% z8b;~mH zWdcD0Vr5)tOXP8_tc4z4$co~D(4)dX{B1I#60iUdS?CFMKL3^Z(G>S?Ba zgoTfq%m!Q)FeF-<+Yd?JRq>!uMWZaZM(!jl6+OMa(>hVnFeUA0HA!_-m&|9OROtmD zhn(Q$1YauSAkcuh*~?wqJi=4Q7_QXy>MnEvIq_!^Udql*Fk|@4T%Ya2)!8)rU-l#H z35p_}to?<3PwbV)Ydlub)jna70CO>hkwgE63OZH^)4dk=~b?@KE%UR4TYNS>(Ddh8PS4C zoP|M4==m!YmmjBW=w%|AZZses|ME*zIUB@d&=B3TS5hfIkD^X~92{)~aY=7xR;4%_ zO(jgQ@xWD=szp;hK!4ttD%vqFh23mE=o(g{76aD2bGtuDCxWDX*iKEFF276oSuC0sK%u_t3dea4wE+jzxkF-e<5>&fpokr$)*#Q=9dDKUQna#*oS56b z_TIh*5r?AqdPS`0=E`ZzAQMDyfh~cl0)WimI5JXc&or|;Z#tG(X6wTvh$)wgpqYa0 z<(^I7xaS)*Sys6mnjX_KOTUu7;wvK2=4Uee|I)|RH4+A)-n*z~Sv;r3eX}~n6!PIX zMWyP?jS2lK)5|>}^O7}EWt?q;y#Fi`t_@~M?t+zzk?{}65Z#RXtCHYRl7nuyDx)S#nvCZ~ zQb*4RfiErXM%4X>UYB;|D&U|g_G0MlBIKfFx$b;WB*mTdttuyF2-V+68hoJO1~@IraG|ZU5wb4zcV>_oyJ!y z9dc}itjDJ>XTw)Wq#raX8eSAFw(ZD32yNl)GczzB6M!HbuE8r9MkcUO!U#0s-^#Tb z&NR7)6MP}i3X^{lfuu{)z0U?*2!qNdzSx|Y?rV@W6RU(~7(Sc|aEb5GWJQkEe`7ek z)V{=~m*lNPFng|Dp$+079U;XJM`xF#W9E(;l0x@xOEp`=J~&Nx*_a0v$mkaG^^ZB1 zo-6tk29QDGkVUB+VJ;*#>~mDJiwgSomnqE18;A+RAUy!i1iT`*rL5H>80KjzBqie{ zhhn=5I!J;jD4PACc6(L^=y++EITclu6UxziDua?{M>lM#uOc6w$vhJ-*AQnxubdn~ z%0KfrB`J$bT_{hpWWQl3wltgQ8^h7BN+5Gw!KgOrWPlea4jE_ergE6}T9?~fmd*R` z^>ea$?~w`Jc`RF4L;k~*$gM<18n{0F4BI}q-O=5>oV8QMeV7BJQ=o9RAtxH9-`!-z zV?pa_EcfRcT8;5+w<$Bwsr#=_1SBeVc$sa@$$)c4)}lfQ=P;9$t%n=7rfSmzw-JKl zfn1TIni`(=4d>MrOk&*!;OiR3=8J+pC7yDRTPXtx#Bii1mtKT~qW?Z3{gySJi@u~L zkhKem)wM$9Rnuc(aQwEXY z)h&>}Y*cUyDkr_8H*ohm5^=6v4do9j*z1Q+2+2ibJUqYkVJfGbN(4O6$5l_7b^UEK z-L=I~$zTh+s`B`Cw=}utp%M&OFn~0-Gf(we0iVggGE=8f!;qEA3=%Y_v?_*m4&IZ> zd6528{jo#W@oVbHK6I)LJtIw)m$VNx;+h{RSJOl0OH;l^W6(GLrTT5^(1`N$c0*`O zc;%DfqoP2w2lQS0T;aNFj6n4*?ai>y9R0*{j&yhuW(k|X)0LGycXaLm>X14~3Zf@{z|HIz)$*i*f$Pbqxw?Th@ZB6@h4CBujs0*li7xx2h z5=y4m1n#vr+OptDcxM>#_x}@OAa3UUTTkUvfhv&yWcjwx!0BeJ9%-^^_)~i1hw>k( z=pL7XBR8J4qA{sH=gjXdM>oiN-9Qh5oidtYI(s+G;YPGg7vZFUgJef-(zRObm9q#!bmNT04>fN++*F$HTb#NTRi~(fcR5Yfi z_oFw3qJVO5!Dbwj5rlS_+%!2M0$NoZFRj-j6mct_>+bGok!p-ZW^hb!-9SuQ^H8Vg z&6kkHFLa<^Wgh5hSulue&AbJa*EX5Pa7oFyD!N_zsrK z26N+n>ijrB^!^_6PBwP)@f=x2WJ@f41)JaFmRO#sk4?9GZKL@$CGk&mp@{>_`D`MPmXx-R3y%vseIc7>;=!XqJ!Xc@QQ`;9EAu_Z|pv$`>avIaH;(b5Ev_T-U&yb^4EwBi(<+>MZR zA72Zc0M`P%pE%K>zPTZpiDvtKj`gtmx#zaq+|maDv1L#f)|P}zS=yPo&|(n zVO{F%FwL&18;fr+__249=>fq5FV zh`1F;WV4tJEK}knmU!IPBE;#ObRHT9M_@uYonC2HFnf&Nh%)sB65`Vh-}F)9X|!xy z@->xYp5f{6vZ`ldk%fB7+Z)54MM}8%_n=AUzqYM^dSf)&pICk_cou^j`86^&XA9TI zMYA(N^$k07B#=1j5l)F8R%TbhU(gY-%1tGtM*Fdu$u|B1yA(hnK`p1L7GNOrfJw0a zHRp@HWjnaa>9xWV+~veeT>l>D-3^lp!MZNn5`h{6)u)*-Ws+I;Zsoi0nl{M=Z_-hr zNZn>d{Y`cWTWGM@AM~CWEh-xagcgQG>)u$Kw%OTSjESnw@>b8ti>2MtQ?U%lM?vvU z;JT`*`L1~eZ0<50v`a9DK@k!A|Tx+v{d zN(f3nAOtFez#xRC)5gEhXC#mr2y|7BVG{d}C5m^5@kf|IBoE+$;KfGcwFn@Xn?{w< zzGpi^&)Uro&RrEshX6o2wvlv^*B}-2)JE)}QPuSB48({dx*i+X1eRo-X#V0jiVCtM zXQ6Tpunxu}z3wvEYwc34*E|{dV;@`%lU&$79Pf zCyd)ijF9`&2dmyekgl^}OX_x5oX}~@t;JRfv5V3XDp24^d)%TAdqJ4qAL;UTL@oE& za&XL)#bn8R;U`Cj36@)t^&?GDeep>^Mkm!+G{AV>!Vg!5aiyJNMiyB%D4sW#7 z%@}>Y2wBW)2zzZJ-xLNIQP2&fQq`&&^oYTg2kIv>_Pu2LTc$8svJwD*9$Cw;FPX#8 zy!VB+H2^@~5`D@~Jc)58XEHU@KwqAib6_@MlHSY~5r`Ts)>qY0BIZ@aNB6>WF)wWG7<11=m3BhbS))WaPooaosbG!5P6!2(ZsnBS_r#rzXq_( zL))sDAs#thKBqN!KFA2FWLBa9HZPdj-1+lS_mF4^oHSWUCJxvUjmm8aIIMyeyoOMRrUo6Tx(q3cO6_N3@+8Jw8l{QN0!>~1$ zDOO0#ygq}s0BQB)G9rvOe82u?GBUZI#Aty)eVr0V@H#S69&l-yMCHm>5vk146+M?q z`7)5Q-6r`!yZSv}m61{|>R4Y=P#Ojw?V$}Sr*aPjMuEVfCkMyC8!92jcC>0|0R3M&ZaLC;1WsNZ~VE(jWZ)4neK`XPZVGQZ< zgg4%Mx&+}ZLuif@XYP9r%|l5>tQ{4tx+q5Rj-w_Izgxt@(wN-pnYmf_zViGWQS_IE z6h#~RtKuFM@^v4ZL@9wUMfUe{7}uO8mrQ;a07*Sh@e`^gf!-a2BI%}lIES8q7y}d> z*7Ez*wnCy1G2=yzPGc>`bD_ZG`{npK<%mB(nmZZc&jh|WMB_mdk$@-Qh-9`6IG>F##F2`(xKV}ll<%*pnn>0~h;4g3-IgzFOA=YHN28CEiA{Ag(7=1@n4iX?H(|}~gu$T@y;S)C zp`lF5IbWRZ9;89rjf^iU43pQFC5da7M+TGWBv-ML(HuK9O*}%aW(mHu9v@JKVm^|` z2>{~u)y@Xlf1fmcLmYZ=|A9W;&&6Z3ZP@q`^0dKi#ta$A;Lp*9NW{_+kx>@uM)D6NC95GI@;TNea^j0ogSh8}IDIa(x8w^!ZFXC~ zKEbWM95ViS7KBlzBTc=#R9vi_6B6<^hYU&Rn9m2Ju@VN9AGqM*5`!{eXqQ;o1%IGk z2&Bni_oXCfZ)M}jn#(tj!=k7Rr0XSMx7T~mF9@s>q|l;goMI;-m}LfVOxOaA@*nc= z4>s@6Bi4DN%jG)&u!1dY2nA`ZU6#|UH$7fzX+gL~S$q5aSbc|l1d0hHbti;$V7L|N zl-VlK9tHrV2!!y9drL?GWwfBedohFTqnOB5GhQk5PSk4>8x$~O4=E?<3n(4hlFFJd z=+~%*8xcLZWtC_C*~8}LrT9ngpHvk&#Q$e{0^)5f$h8v`x%lv&CM?jzpP%F5snJx9 zkOP{&3V@i1=l@=`?;5lYGT~u8C2JHY9|Igy;#9S+aL11K1*YQ#^;>*pmW+eE^m8KR zjaNMsL61dD0aLDYj)T04J z#f=7LF$ip#tqFM2&R+nB{;7oE*jzMCTHJ@fSBgE14{m^nVanRa-fcK3s|h=3GHx71 z-sqLhqf1zU(n3%^hYrvAS==Vn%$nDa8(8a~X-9WDeS#8gkj^@xN-b7~wmauj2U{U{ zia2*nAx<4h#oGxX&fLpVh5u{0LPShhO+9cPhUMEu)BsEY!oQgXl+l8z9v=t*5I#L% zl@nHPI!C;=TLH2GrBx?7tB|yrSr6tqImKt7F;n}5$(q><0pf>y0s@}!dri%|WsUrZ z@zPXP^x^}>L{Dwv*d}gmOkI2(++v2+Zh-+`FQCw*AhI0!Cf8J$0c}&!0H2i;C}i04 z9jYX`8ghfv0)>a%O`5h&Z10?g=ecKu9_9n%aGJd&@{s#dO`YVObkHjVgs6CH1qp-0 z)mzt)#F&YP?`)z}#Kk1ulgz5Oxth}%asMOQMl@x(VuQvd zk@2R4^o!8Sfc=E@;S^WtRKhNQvzw!GgQj+%`LRb-07msyPm=CUUYtefkQo(3cqbx( zAXIdbAIe`RjCnKoPn#2966&a7YHu-YZ`UWOz!)sG3?Vq>v*i$5sj&9!eO4ufvpW2$ znB?{Em{^Ts4ylp&)Lf=!q?C~+7_TRN*d|b7b%c*m&Rcq=n_R05!Q@L@5=(4 zsY+CxXgh-B!n}HkWP_m~_y$5J%Zt4>3U~qOLHBw3^x~*r;TP=a3)i02Fo1`Zb0m12 z3ZBKvpRIh5q-2mCOl}6sk5dRxfyA#^aShxt)O8X;%g@yrvj_9Wj;EFelywncqy@UD z;F&=KW@-H4&PUsgVWn1-!W*^H&+Ip4j%46NSF=}I*B4f!jafEgcoZ!F2R^ELdEhLs7XcC7>-purgHDC# z@~8$S=a6qRunb61eygq5R%v=Ujt}{C);Th$|7xmHVS1?{lah`>+>L`1u3Y6-PuB%} zA10oxXXU$=*GD%rsu%NXbSa#)9!^H6>;=8S%m40dQgq$QKs7ghbEfsN#1tbkITKV4 zwO+${Lx6wT<&TdMmd^BU+kiKcY%Du1t>K{IX)R){@f{J$u|gNg%`fd$o=((1X0QxmLiB( ze%i2`KcW%|3LoU_&wSw80!KD6PkwXe$vk zhgX~d@gwPFq<)pt>DD^o1=RUWw(CR(jQH@$)zn3Wn)i`*+q#1+aDK}|=hDH@uBh9? zOC6sdp3UQ0d}hkR^4#Fzxx65(y|%!;g6+C;L%?b!0^ z?SSimP?-2#aTdwlbDhj49{5pOIV+btNkxV!#5Nk$TUEz@ZZ9g|e*~Qp?nnsEVv@q= zcb4SCAg|kz6Gl#)k-Ay1NCLWdaRE-0#RT9j3HOlX8T8gkT=e>2XCUVz`K2jXn%w7f z#go7)p^z3S4YZy~ki9{$@(HK_QqI&H)iE*RlI@Y?e}^0AE}>bX_`V@O>v~a#5R0W~ z3wIuac*E)`S}k%)nhHGYcKK1G2QJ$F3+(i*VADyX1{HB5Vph+kK+KgZMUH4tNC^GorgM1LYD}u#+3S+v*tlGw+tQWkB9iyh+QI_to(+n%B`*J*_z%sdCMbQtkn@3~-8^7TbsCL{ZYhc&M-S>x-zH zs&ijIuye6~Wbqt;Ae1CyNbPagvk9zrN9SXt#{+;@ZUxwel1WC$fjA#KkESR^-C9%w z!E3@gAFNM+tNZNUwF%#C|mZuu0c5nZ#+{@X=ok!LKP%7{d=2w>R*nvhK_5BNe* zZ%XY=>a%(X+Mu}Y+JY9HV`JR56loE2T=610gtA6Ok zJn}hd8V`gpDNN$t$V-CT%f)hZ8=@;7g*x(GFYYe8} z;xci$&~Yx{7&fS8p6DUGf;c=gw!hO-jrYXT`HbB@#U{>%HS`u(R^f*AFqEerqqhjp zL8x2O1k@wBiJ6>K{B`hamgH|(xP{bz;STEyo~&m1i8f+*Q+p}HYoerEn1~3(CD}R$ z`AyLip`8?PYv=d^iOnD|(eP7(3IRza%R7_y3-*o=7xs#HQ@Wu5OJ#n1CKWgNOxZfy z5hV3s2nxrV%@UQnIW4cK?oqYYrEG@*W!2Gk%t&rmOb!8TYrVF3B+6c5ftgnBHk zu%130tUP6Wz-ItbvziU81e{(easpB;$iL%Aive#MI~fr*m6t4M zoGk2kNo%i~brpJRh0v6!Sy$}oXzyeq$G)@`;O`uCH~#=a+>3M^Iv(P2ThKU0Bv#n^ z4||jibNeT1WOaC(MwJkVP!wf+YOr~qXM`Nol{<6kAy5tml0B)y(U(OnBTgb&QFl=nNFro;Ddt@b0`)S5@N3DH^>Sn(DmujOPi3iWqOu> zRBG>7n@O4%;T45pFf;i7GTYPiC*WH+;cInOmm?&v0F!Skm|swn&SY+w3gq~HGmyXW zpW?tQK|JkCQdm`=NovSaOGChx++4-;zGgDxC+F7up8lp%;M_Gdp13er^wr<7Ie-Y@ z8E_?ffO#NffI_fq`hA%~yMaNATpx3?6O3B$-3J$I#%l+GZ8+_U-vvhS8ma)O6uqH9XOrubza1cuDhot|V}-a?KZN=Coh}^TrrV`uW@L-^51?-& zx>h2-v4#@OC;q-h0&qe_M)`0GCfwkl7oW!=X;&1XE#0JUE)U?bb&{Z%s7dl0Km9q3d&|)V>BAV|9EfT z$e%bavPKS*92}c2vE=5T1W2-(jz^%fox`j{w8|XN2Ys3#itlsfI-XQ8)jh}e7f*2c zE~Opa_V5io)EXI@jH&!L-=H1&kC9c6HiM`Y#5#CoKINRf3|S9&A(bb9D1DuKJ4g_D zXs@Z<-rN;&U72-4XDdgvS5TZ!e3RMyLQu2x%Fr--+>RvgV%xDBo?(}Cin@|Y=7ifB zv2}%uzDd>-M3Cj+>f0U!t6evv2h?1T{9CGX0NB!kgPrMkqNDs2M3jrESmDHD0VBk= z>XZG!PE4u(&FE}v%Im1D@94?Y=h%xACwnqaIGAaeGv6+wL8nD5uM>3)?> zC;=PnCKe4<+kyh-8RNSm?Qh5f>ulNI*>W3Kr><*bc+;@WWDf^CK{uH@aOBy z2Ks=9QVdP$kcf5KlRs`Sa*sSgHAlhgfrzKUG@TNoC^;v43j|tmrOy9Ka6}|}+dL;m z&}h**DxP)ehC)$_IGn-b<)T{+O8ncnnVi?CXSTx3!43v=Zsra#T4<1PvoLX|-gD?>q2AR=o~tfmeY%w;m(F!c09or^=*Zcvp`-j+%Ssg9bOy zG&E5!GF*l~l=x6}v18YDYt1O)KGzKZlnAJFeg{BHuQoJ~!w_f8gJWFaAfaIs7(9$ef z!w6N+e3Yfs5ZWOyz&bA)GY_C7RwCS?Qx85ORa`PUM__oA)@m+v!yGK~W@_?TzY?$q zGyD)2nX5%+91I`}3;}8niC`?mA;l8h&)6JW{9+Sa!Tlp`|6o5r?JtQqrAQs9)`XWO zpuTv(F1Oo!I}IQpjl3Nqh|aASfDrZ-@O5yMmIi2K#>!&`7E)r*gZyXbf0jBI)`3HQ zQ2K2XNSIQYxXCAx5t30tpZ)?EFVy_>A9zGfjpU!pp8*hjI*pCYQUUYH{QioT&6h!ORm0R3 zA2GQOID?dLsRj`p$5KJW0R@5{(=bT_#_$(jlNBL;b&0EV(ET`o*klZ>`IQw2F~Z zYE%t@$QPOUf7vNR+BnYFX#*|NxrTYf*U5Io7C&=)y6cy42|?CKTwdff97Ysl2*OK!uYf4u`9Kan8gnHu%IxnD+ve(B*B&6#- zb=I6*#d}EI3^t=;5P)%zpJe5)Unh-I1?9z7&7Sf53unQ3>Ehf?&0CtO#x#hA2!Ha1 zu>l09d>Q|IgZjJH9x!>*CG-JKvm+~=a9;iuMcqTXg0nyb_vkR`rJpFEWzMdSG4e*R zWbuPkLlR#XtcdfgfT2v~MXvT3k)v*}*}=1#rXOVkIS!@!yLkx)?PGF4E|SK)Snd+L zt05Bx-4@sA!HJ*DMk;knC>TR5@*J#iHAgz$=2hZ-p&jTnL6{%NF?^Q=e;l_l>{J%z)8j6r$U}m9sy|W`!vK(L9(`XS z5i^`SA0@wsHn7<)${7%+KfzvRA1oq&Qx!d+O`T#HGzA<|NGBrRb*ffQcm@{q$U6o% zCEBHEly~XvXIeJ1zIX2fTUu%Fw~5@zGCz6?Qt{j=hkg*5&|@wyby+)GO@GBG(->M^ zA@xfmqXlOx(1Q;~UuILtuB*hG>nZhs0@lP!*omcV7gPVv%`~!sOm0tjFXNxB63&kv zXNN->AMk*F0Ly*TVN%_44Eil88qLg3lhQtEnj~+LGyu|xz&iQ1v<}eCJQ&Si!XxUS zu%8?QCC5MaEZdN9Kw{&u;4w#|B-l_edVlyjM5PXOb_fx@Lgh-31FPirF zZn~-;wUwC6aiJ}MZ7JU?b+5=w4`qjk9g!|3BQMWm(RG_k-LUD-t)5m7a;Rqmyc@<2 zDq{Egqhi7@;&OedlQ^k4Ov-wx113rfLbYqEx26IARnE$`hydmp(`+u^Nlv>TvbtMb1Sw$|qC6#Qn`Uo#pse@@kM z)52)F06sa8I&LGsm_5PH9g z6a-ls^30Flcm2L4v_Nw}#8x}JJ^9l4(RjL(qh#-=?#UFeorl*RFNG9E8sG(?jrWfM za2t$lgsyX$Vur};ErLjA}A=X;-`rOw_NcrN%(-Kqs0WEBFk40 zyC<0kI5pRXBnW*X&aDRj@m8{SsJ{s0D#-1Z?hbzXTFrbo7zgTTq#v&nV$^ zGAe1CsUk=ziYlHCERhagO?j6S_H4N>GSVmb1)>FaeF-XcT7k$8#_bJI)*aeaejT6i zYSv#A@3P&fJp&Jrlv~z2_!?F_z7=562vs4-;VO9PMFEu{Q?yYC9i&NKj zO$-Y$mZr+OTY>IE@fNy&9b(wm{xzlylPV`2? z!1f3pL*if_Aael-!%QXDGp#1n4>yLwL1!P%@=$#7+HimdvfQduxTG1H@vmFV~8q~*{bT`2&-QDN%ULlM$T#j=n z4oE&h+)v{EQ)5j!h5f^(OobQw%9H;WR;MCHLW=<~MB(x}h@>0~6dm9kHNPPcKHjma zhJVfatu>1dnb%7kV^54rLaV2Ap`3lC+MEE$qz?!P?vR4qx43`35n*#V$$Ezj?u_&A z?0!nBp;oszL16kU^+T?3iAc?vD{^8YX>-{WFqsV>DwT|IoEI_bFL5O z7N^~)LGK()B(SJVmA~tsR=G<>hg6uT)PO)JmXru$00mI9L?G@Q>eV)U?isbh*UYTV zInYi;<=<5tz%4ahBA-w3A-_~1F!OJ6R!(i*{|M?(c_H}H#G|knCB?n&L>x7Nce2KX z;0pA;NFC50CJO8QzY4G3p!d!JThquLUHZafnxA>lw8{dX(F@j8pk!rP`0mdn&)M-X zXd#<_aXku))TBl;JOH#)0WHx$b)fprh-K7%LwOfbn$?vr_Yo>jmPDGK(yv;7|E0qG z&+L#?O#o>9BOK5-=FW^OB@gu1(d%VGozC3v8}lM? z1Dj3()AM)50!;xppXEkw>-~ltljpz*nNr~SYu^4JHsyBoX8E9~q+z$ehl&~^b&=U|PF!R?l1 zBC%jC`>9ua49o>9j5aS6jxh$kO|cP5_l4Z|^OrpM9H8h{f)Ii#e|ZYEU8s0>D3K-8V!o8o=W6 zMp@z2C`NW&W0S?9MghgUO@fuWTR0iuAq#x{iI%hI!f($p!y#mzh3K3wtp{2@b* z%FxMGW{Q#=^WYB7QtjB$LFMd8LBC@>u(*#oi^k-$T7PvFe+F~#G9|bp(8N3OzHfnWS}=7Ta2GlK6yMP?*!zKK)ikYD3zIAc;5yU+AW`RutlEd08-6&i zQu+=(IzYho*{V@RU3WQ9=`r9$8))R$%EXTdq56YN^J}xTp+SK9M*^8mE(-%lg?$Em z%|HR)mdU$mCoNIJw+#H4sg*^lPapfpO8Sp52C8foeMXmT;4Z(zk4Yw>gkF-x?u%hl z@@u_986g6VbtLd|plDN0uWF>Pv2wTsB)*Xp>h5B$MEDf4GW+B4BI?dJbf?VuCKnT= zk5kd{0{-Q~E#zE55b6AAmK|(Q13`}f7i#;NMF+^+G<|2R{Sa>t2u+)+Dfz|;k~7%e zCe24nYrK6gK0vOepdzI7wn_s`YT=-ZXBPT{FcW%$r1*E)_{Ky%u#-N4)1o+;($s?= zh?t)~72zm`r|@yZ2t%wq^w_upQkFFw1jfKqD!_bTheCjg)3gDV?w?RvcyLd;)Jfrk zzDPF~RfMAO4~-0LyoD%nL0WDYfO^g-56R(V0M!VM`Aws!3I%7e{>uds8>@;;K3m#7 zqs|)w=EkA)M`z4zQA`X|OAVY)bt-fZ1m{1Z%=9h`yQ-#k3nHiRecI`I!A_Q+>Bbh= z2Hl>r*~(;3^gv5`z4*#Ek2jdFAe7yppW&1^-{aBbvAX%s0&TVq(oP_q5ah9f{q+-= zLvGL?4>T=)O5o23RevjU_=NrYQqg-IJueQrbe9(zk=X-jJwl8lxA-SCVNA9K!d(L$ zf`h$Uxk(3!MD)kxO-&MZGXDuU~8LlFd%_uJU5(yop$OhBVOb28)d5_#CHG<{;qnE74QrBDz0nHc8#*)Ezkw z!j5%y(CKR71+X-x6h~W$pkssqG|_QrZ;8CzGnhycW@eYu%#~vFf#W^1c#too=je{^ z`mpz5^(0Q9xq_g&fp=%uyeq&>h@-B8Fsv%Gj-NBJ7|LZ?6{BRk7<*BHra>*2^Px;} zTO~z@LAatsGD^FXQfkcNrzp^G`;MjDGI+M2Vh0u!ufAr6nv`lAhFMV&^zzE?2iU$`qwYb`OZeF6 zF*MS$%-nuCJ?ttzHN%fGBwq|}Sp28z22clqgKp@)T%TgC;z}of^Zxlf>D_yV4}`HU zD?{+aCAETy&JR(VHj4^;i>Cx2ridu{#e+M4YvsL;;cIgmZM}(^NTd@t_%2uvf-}a*lg$eh!pUBN zM@neJ>7v${T+?&OTPEfJtXOO*l$6~q6LOa9N)suM~MDlp6nPiq=%mT#lwyhY!DknZs%STEp30ZNYwm%6g3rL<79o z?TcG3J`(O!2p`^{(tp8pFbngu;Y7Pqd!bMP<%su7-iDj7QEtQIcB`gU(p5TI@ za$gyQmxZ^6i@Px#bzLvMVk(Kgok-!LtWgi{5MSa@*%r!cPi3zAsflOrDd^V~Y_;{> zDipJ=!)CH{grCaQ98`!SQ=6^2Inv)&KFOKbRx`ZBHoc(rp$0)Cpb>hqR07{N>*J^) zEC|$$%??f$mv?u6wFmMaE+OXpY7~MU!VF_@BH~Jwr3`XHOpNsBCk;RB>MM$(Zslzh z&M{!1L)jWcCrSpS!u(N^Fk&Z3aWp*@2sT<2>F%ik3^6M3Umk}-8>dGn>3Q1CaBpiQ z5vv*9isufHO2rMYnT{{qBV}fu`~zlb!!hQ={~2U?r$T)(QCQLjCv~r8Qv)PC3qV+C zIdZDw*aC2DUwmshzVUqo?014A`uD7}U)(wrI|pqJPlU)HVZ^@p7*lQb9RZYZ7)^yvSAo6| zw`#sulYgt1kLX$910Ka{V4CQD~4=2BAj zfPvYaQq;wjm+b~G>SnzFp$X25T{7WwItg?v_=L47dk#e25Wx*w+UTxeQ&kGTYMEcs zI}xN4A0Rr2Nr1u>)sQZbnZ|stxB6W|kC88Qzj7z=Jigl_R4|8M-isgpllSn2kSxS;->J9j87I>2U1YbL8%T z3K?bqM{krXX7)xcC-J|1!~i)`*JyDTZ{$V*#k5)Il6T-EADmK9o*OY2@APTtjUi#w zM0qi3B!IJ4#|xwqB&-OcHJ3xsSe@Jrp89;>2*BMPk>odWiuNltwaENeDG5q|7B)p& zO-R(#sN~>cVeUzr7ZOcdQ2^-SBFc~wB!mrM%0H8PD#U*T%l-8Y5FvG+?JRTJ%#r z1C)p5TB^_P#7zQp_T z4>HGn9(=`YR>L+Zh(58Vxj$$Er#poMKfdV$TYqh=&fx1196a+!du!LYFdzyAdJ7I6 zQ+pBCXMRX)kb|svm|PGpDd}DQMUZ=599w`+U1fkKmeZTP3;tTzAc zHdDyM6ve-kQfXeWd<7zUqDrG+v^Qkbve1A*OsU57EW=d}pgu)yXYtGtNvLw;RPVhG z&BNTggJt~s0YKIIEgJ@9Dvwm!gX*t17KW`Ee@Vgq2*Bzw3OYjsQ3IMOd!)Va6h9qA zgv~HAYN0AjG%&RjB~{r{+B!QlrP+A0*M@jF@XVg(|F+?t%%LXOP)t4p1KepNOV5O6 z4QSR`Y5>Wv-X7s5mI&fk;%rl;g=?T{D7nd*QXIW!iMPqlu9}>xo+3-X+EFdlpyd2_ z%>)}XUg^m^21a`!DBr|`UScp$nQ@9E%|q_s_|(3dqF)naZK$=N@!lZgJO`=S5Yd*Y zWdt=iUM-C=Ice(+G9A}>wgYa$u}|B;cxU9z(jnWwMr8#P6!l3+2!P}>rqZ7WJP7?B zN%=>_z~4ndVtIH!fG-!#>b|thh)mhgWd4>5Kk;9XClAS z`>Xv;wVrssvsU6sG2_61+DI6>_G#8+P+I@IJkVE!i+`T$wF`4SIEHwjMS7>0lS`)NjrfL=)U>!a67NQvoRfxdVn8=~_-|9EbQ%IwM25du*Thug^y0?Q4IQBy zRT(F%atvc+d9IL^`pFP_%Yesq2OI?1xHUjz`A-J{@PE5YIqaAZfj1(W5bP~_1JnJd zX=GerZOeJ1V^&i`Vbk?-9urOi;9HE4eCIDeG>03F9zHa|~ zW#w4VEvTs>ikN3x2LyfvRksw}IYqFuEU{DI^(l}o_+r8?6Uaxx_I#Z5BNq2 zjS&suuc(1iyP4baO?odt-6kzWa8_B%6q1C{KYTUH_DQhS0`pUgKv_Bi_4MFrJPWq8 zpT5PZQ@t%3%cYuy7Q*iVKF#rmoZ4rDAiSnyFTW_*c5EHx)uEZ7!-%k3xuH+*TNLN) z3q&ZIyz>~#(&_WLaZ`w(+iw(N5jFYK9hB8JoiaGded%~^)RGS|_oK>NBpWPWp<``e tMH|?_FuA5XYB-;@U5r33k_rY?_6+DPvn&n)pu8lK3d-aw1!M1@5&)N8ody5^ literal 0 HcmV?d00001 diff --git a/tests/test_idempotency.py b/tests/test_idempotency.py index de62c04d..d1a10c09 100644 --- a/tests/test_idempotency.py +++ b/tests/test_idempotency.py @@ -18,9 +18,11 @@ "test_jpg.jpg", "test_gif.gif", "test_webp_lossless.webp", + # A lossless JXL, which is re-encoded natively. + "test_jxl_lossless.jxl", ) CBZ_FN = "test_cbz.cbz" -FORMATS_ARG = "PNG,JPEG,GIF,WEBP,CBZ" +FORMATS_ARG = "PNG,JPEG,GIF,WEBP,JXL,CBZ" def _pixels(path: Path) -> bytes: diff --git a/tests/test_jxl.py b/tests/test_jxl.py new file mode 100644 index 00000000..20833996 --- /dev/null +++ b/tests/test_jxl.py @@ -0,0 +1,213 @@ +"""Test the JPEG XL format.""" + +import shutil +from pathlib import Path +from types import MappingProxyType + +import piexif +import pillow_jxl +import pytest +from PIL import Image + +from picopt import PROGRAM_NAME, cli +from tests import IMAGES_DIR, assert_size_close, get_test_dir +from tests.base import BaseTest + +__all__ = () + +# orig, no convert, convert to jxl +FNS = MappingProxyType( + { + # A lossless (non XYB) JXL: re-encoded in place. + "test_jxl_lossless.jxl": (92889, 91811, ("jxl", 91811)), + # A lossy (XYB encoded) JXL: never touched, exactly like lossy WebP. + "test_jxl_lossy.jxl": (77911, 77911, ("jxl", 77911)), + # Lossless still sources convert. + "test_png.png": (7967, 4152, ("jxl", 3927)), + "test_gif.gif": (138952, 138944, ("jxl", 115204)), + # A JPEG must NOT convert without the extra opt-in. + "test_jpg.jpg": (97373, 87913, ("jpg", 87913)), + } +) + +JPEG_SRC = "test_jpg.jpg" +JPEG_JXL_SIZE = 78762 + + +@pytest.mark.parametrize("fn", FNS) +class TestJxl(BaseTest): + """Test the JXL handlers over the shared image fixtures.""" + + TMP_ROOT: Path = get_test_dir() + SOURCE_DIR: Path = IMAGES_DIR + FNS: MappingProxyType[str, tuple] = FNS + + def test_no_convert(self: "TestJxl", fn: str) -> None: + """Lossless JXL is optimized in place; lossy JXL is left alone.""" + args = (PROGRAM_NAME, "-rvv", str(self.TMP_ROOT)) + cli.main(args) + path = self.TMP_ROOT / fn + assert_size_close(path.stat().st_size, FNS[fn][1]) + + def test_convert_to_jxl(self: "TestJxl", fn: str) -> None: + """Lossless stills convert; JPEG does not without its own flag.""" + args = (PROGRAM_NAME, "-rvv", "-c", "JXL", str(self.TMP_ROOT)) + cli.main(args) + suffix, size = FNS[fn][2] + path = (self.TMP_ROOT / fn).with_suffix("." + suffix) + assert_size_close(path.stat().st_size, size) + + +@pytest.mark.parametrize("fn", [JPEG_SRC]) +class TestJxlFromJpeg(BaseTest): + """Test the opt-in, reversible JPEG to JXL transcode.""" + + TMP_ROOT: Path = get_test_dir() + SOURCE_DIR: Path = IMAGES_DIR + FNS: MappingProxyType[str, tuple] = FNS + + @staticmethod + def _reconstruct(path: Path) -> tuple[bool, bytes]: + """Return (carries reconstruction data, the restored JPEG bytes).""" + result = pillow_jxl.Decoder()(path.read_bytes()) + return bool(result[0]), bytes(result[2]) + + def test_convert_jpeg_to_jxl_is_reversible( + self: "TestJxlFromJpeg", fn: str + ) -> None: + """The whole point: the original JPEG must come back byte for byte.""" + original = (self.SOURCE_DIR / fn).read_bytes() + args = ( + PROGRAM_NAME, + "-rvv", + "-c", + "JXL", + "--convert-jpeg-to-jxl", + str(self.TMP_ROOT), + ) + cli.main(args) + + path = (self.TMP_ROOT / fn).with_suffix(".jxl") + assert path.exists() + assert not (self.TMP_ROOT / fn).exists() + assert_size_close(path.stat().st_size, JPEG_JXL_SIZE) + + has_reconstruction, restored = self._reconstruct(path) + assert has_reconstruction, "encoded without JPEG reconstruction data" + assert restored == original, "JPEG did not survive the transcode intact" + + def test_reconstruction_jxl_is_never_reencoded( + self: "TestJxlFromJpeg", fn: str + ) -> None: + """A second pass must not strip the reconstruction data.""" + args = ( + PROGRAM_NAME, + "-rvv", + "-c", + "JXL", + "--convert-jpeg-to-jxl", + str(self.TMP_ROOT), + ) + cli.main(args) + path = (self.TMP_ROOT / fn).with_suffix(".jxl") + first = path.read_bytes() + + cli.main((PROGRAM_NAME, "-rvv", str(self.TMP_ROOT))) + + assert path.read_bytes() == first + has_reconstruction, _ = self._reconstruct(path) + assert has_reconstruction + + +EXTRA_FNS = MappingProxyType( + { + "test_bmp.bmp": (141430, 141430, ("jxl", 40804)), + "test_pnm.pnm": (27661, 27661, ("jxl", 11840)), + "eight.tif": (59640, 59640, ("jxl", 21781)), + } +) + + +@pytest.mark.parametrize("fn", EXTRA_FNS) +class TestJxlFromExtraFormats(BaseTest): + """The pil_convertible sources reach JXL through their convert chain.""" + + TMP_ROOT: Path = get_test_dir() + SOURCE_DIR: Path = IMAGES_DIR + FNS: MappingProxyType[str, tuple] = EXTRA_FNS + + def test_convert_to_jxl(self: "TestJxlFromExtraFormats", fn: str) -> None: + """Convert BMP, PPM and TIFF to JXL.""" + args = ( + PROGRAM_NAME, + "-rvvx", + "BMP,PPM,TIFF", + "-c", + "JXL", + str(self.TMP_ROOT), + ) + cli.main(args) + suffix, size = EXTRA_FNS[fn][2] + path = (self.TMP_ROOT / fn).with_suffix("." + suffix) + assert_size_close(path.stat().st_size, size) + + +class TestJxlMetadata: + """--strip-metadata must reach the JXL encoder.""" + + TMP_ROOT: Path = get_test_dir() / "metadata" + + @classmethod + def _write_jxl_with_exif(cls, name: str) -> Path: + exif = piexif.dump({"0th": {piexif.ImageIFD.Artist: b"picopt-test"}}) + path = cls.TMP_ROOT / name + with Image.open(IMAGES_DIR / "test_png.png") as image: + image.convert("RGB").save(path, "JXL", lossless=True, effort=7, exif=exif) + return path + + def setup_method(self: "TestJxlMetadata") -> None: + """Make a clean tmp dir.""" + shutil.rmtree(self.TMP_ROOT, ignore_errors=True) + self.TMP_ROOT.mkdir(parents=True) + + def teardown_method(self: "TestJxlMetadata") -> None: + """Remove the tmp dir.""" + shutil.rmtree(self.TMP_ROOT, ignore_errors=True) + + @staticmethod + def _has_exif(path: Path) -> bool: + with Image.open(path) as image: + return bool(image.info.get("exif")) + + def test_metadata_kept_by_default(self: "TestJxlMetadata") -> None: + """A plain run preserves EXIF.""" + path = self._write_jxl_with_exif("keep.jxl") + assert self._has_exif(path) + cli.main((PROGRAM_NAME, "-rq", str(self.TMP_ROOT))) + assert self._has_exif(path) + + def test_strip_metadata_removes_exif(self: "TestJxlMetadata") -> None: + """The encoder reads EXIF off the image unless told otherwise.""" + path = self._write_jxl_with_exif("strip.jxl") + assert self._has_exif(path) + cli.main((PROGRAM_NAME, "-rqM", str(self.TMP_ROOT))) + assert not self._has_exif(path) + + +@pytest.mark.parametrize("fn", ["test_png.png"]) +class TestJxlIdempotency(BaseTest): + """Repeated runs must converge instead of growing the file.""" + + TMP_ROOT: Path = get_test_dir() + SOURCE_DIR: Path = IMAGES_DIR + FNS: MappingProxyType[str, tuple] = FNS + + def test_repeated_runs_do_not_grow(self: "TestJxlIdempotency", fn: str) -> None: + """Convert once, then optimize twice more with no change.""" + cli.main((PROGRAM_NAME, "-rvv", "-c", "JXL", str(self.TMP_ROOT))) + path = (self.TMP_ROOT / fn).with_suffix(".jxl") + converted = path.read_bytes() + + for _ in range(2): + cli.main((PROGRAM_NAME, "-rvv", str(self.TMP_ROOT))) + assert path.stat().st_size <= len(converted) diff --git a/tests/test_jxl_detect.py b/tests/test_jxl_detect.py new file mode 100644 index 00000000..db5777e3 --- /dev/null +++ b/tests/test_jxl_detect.py @@ -0,0 +1,104 @@ +"""Test the JXL codestream sniffer.""" + +import io +from pathlib import Path + +import pytest +from PIL import Image + +# Importing this registers the JXL codec with Pillow, which the encodes below +# rely on. +from picopt.pillow.jxl import is_lossless +from tests import IMAGES_DIR + +__all__ = () + +_CONTAINER_SIG = b"\x00\x00\x00\x0cJXL \x0d\x0a\x87\x0a" +_SRC = IMAGES_DIR / "test_png.png" +_JPEG_SRC = IMAGES_DIR / "test_jpg.jpg" + + +def _encode(src: Path, mode: str | None = None, **kwargs) -> bytes: + with Image.open(src) as opened: + image = opened.convert(mode) if mode else opened + buf = io.BytesIO() + image.save(buf, "JXL", effort=7, **kwargs) + return buf.getvalue() + + +def test_codec_is_registered() -> None: + """ + Importing the module must register the codec, read and write. + + The import exists only for that side effect, which makes it look unused; + autofixers have removed it before. + """ + assert "JXL" in Image.OPEN + assert "JXL" in Image.SAVE + # picopt's pil_save always passes save_all=True, which raises KeyError + # for formats missing from this registry. + assert "JXL" in Image.SAVE_ALL + + +@pytest.mark.parametrize("mode", ["RGB", "RGBA", "L"]) +def test_lossless_encodes_are_lossless(mode: str) -> None: + """A lossless encode is detected in both container and bare form.""" + for use_container in (False, True): + data = _encode(_SRC, mode, lossless=True, use_container=use_container) + assert (data[:12] == _CONTAINER_SIG) is use_container + assert is_lossless(io.BytesIO(data)) + + +@pytest.mark.parametrize("mode", ["RGB", "RGBA", "L"]) +def test_lossy_encodes_are_not_lossless(mode: str) -> None: + """An XYB encoded file is never offered for re-encoding.""" + for use_container in (False, True): + data = _encode( + _SRC, mode, lossless=False, quality=90, use_container=use_container + ) + assert not is_lossless(io.BytesIO(data)) + + +def test_sixteen_bit_lossless() -> None: + """16 bit samples parse past the extra channel info.""" + data = _encode(IMAGES_DIR / "test_png_16rgba.png", lossless=True) + assert is_lossless(io.BytesIO(data)) + + +def test_jpeg_reconstruction_is_not_reencodable() -> None: + """Re-encoding would drop the jbrd box, so it must report False.""" + data = _encode(_JPEG_SRC, lossless_jpeg=True, lossless=True) + assert not is_lossless(io.BytesIO(data)) + + +def test_fixtures() -> None: + """The committed fixtures: one lossless, one lossy.""" + assert is_lossless(io.BytesIO((IMAGES_DIR / "test_jxl_lossless.jxl").read_bytes())) + assert not is_lossless(io.BytesIO((IMAGES_DIR / "test_jxl_lossy.jxl").read_bytes())) + + +@pytest.mark.parametrize( + "data", + [ + b"", + b"\xff\x0a", + b"\xff\x0a\x01", + _CONTAINER_SIG, + _CONTAINER_SIG + b"\x00\x00", + # A box header claiming a size smaller than the header itself. + _CONTAINER_SIG + b"\x00\x00\x00\x02jxlc", + b"\x89PNG\r\n\x1a\n" + bytes(40), + bytes(64), + ], +) +def test_junk_is_never_lossless(data: bytes) -> None: + """Truncated or foreign input must be refused, not crash.""" + assert not is_lossless(io.BytesIO(data)) + + +def test_buffer_is_rewound() -> None: + """The caller's buffer position is restored, like the webp sniffer.""" + data = _encode(_SRC, "RGB", lossless=True) + buf = io.BytesIO(data) + is_lossless(buf) + assert buf.tell() == 0 diff --git a/tests/test_timestamps.py b/tests/test_timestamps.py index fff3b1fa..f9bf7241 100644 --- a/tests/test_timestamps.py +++ b/tests/test_timestamps.py @@ -26,8 +26,9 @@ DEFAULT_CONFIG = { "bigger": False, + "convert_jpeg_to_jxl": False, "convert_to": [], - "formats": ["GIF", "JPEG", "PNG", "SVG", "WEBP"], + "formats": ["GIF", "JPEG", "JXL", "PNG", "SVG", "WEBP"], "keep_metadata": True, "ignore": [], "near_lossless": False, diff --git a/uv.lock b/uv.lock index 85051a03..0783a30f 100644 --- a/uv.lock +++ b/uv.lock @@ -1640,7 +1640,7 @@ wheels = [ [[package]] name = "picopt" -version = "6.6.3" +version = "6.7.0" source = { editable = "." } dependencies = [ { name = "confuse" }, @@ -1653,6 +1653,7 @@ dependencies = [ { name = "piexif" }, { name = "pikepdf" }, { name = "pillow" }, + { name = "pillow-jxl-plugin" }, { name = "py7zr" }, { name = "python-dateutil" }, { name = "rarfile" }, @@ -1713,6 +1714,7 @@ requires-dist = [ { name = "piexif", specifier = "~=1.1" }, { name = "pikepdf", specifier = ">=10.5.1" }, { name = "pillow", specifier = ">=12.0" }, + { name = "pillow-jxl-plugin", specifier = "~=1.3" }, { name = "py7zr", specifier = "~=1.0" }, { name = "python-dateutil", specifier = "~=2.8" }, { name = "rarfile", specifier = "~=4.0" }, @@ -1918,6 +1920,86 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" }, ] +[[package]] +name = "pillow-jxl-plugin" +version = "1.3.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/75/810608d489c2d453e16a0866de5edea2bedd728e8830da22a1ec633f3b80/pillow_jxl_plugin-1.3.8.tar.gz", hash = "sha256:4430fd7757899742079c548a7d2537d6d63cf0f1d323180095bc0f6f03d9d4fa", size = 1602233, upload-time = "2026-07-17T03:23:50.387Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/2f/13fcf0700d7de9e270dede2448a39f2f39764356f48b030e93aa00b378c3/pillow_jxl_plugin-1.3.8-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:8e5917812ab609951211c771726cfc99f161238b09a9175ea1fb122c94227b22", size = 2360623, upload-time = "2026-07-17T03:22:54.101Z" }, + { url = "https://files.pythonhosted.org/packages/95/96/b9e8c5521590308a9ae441f163a593af403130ff12f6e954f9710d18d369/pillow_jxl_plugin-1.3.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b1f80e18334215195eb7f5a19e20b8448778740aab6ffb058da551c1c1d95b70", size = 2667999, upload-time = "2026-07-17T03:22:45.733Z" }, + { url = "https://files.pythonhosted.org/packages/af/0c/ce07ec0035dce02cfc00df05900b8b2f129c170d55e62eca57470353c49b/pillow_jxl_plugin-1.3.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b4c60dadc9656e2d3093310c64fc8b521dfcea299913838bef9ff496e777f65", size = 2791586, upload-time = "2026-07-17T03:22:18.684Z" }, + { url = "https://files.pythonhosted.org/packages/53/e6/f72e4c8e3ea000bb9472cd83bf3a7e78f5f6ab34ed22ff6c2e1743d5bcf7/pillow_jxl_plugin-1.3.8-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:06a0d4d530c8e36f098301f426611dac4c3a7693cfb3121663f1ca8f71ba4517", size = 2504840, upload-time = "2026-07-17T03:22:27.829Z" }, + { url = "https://files.pythonhosted.org/packages/fb/fa/92212283b7fe3c71cbabb9443b8a8ed940fb73804bdebc86d08e52cf8349/pillow_jxl_plugin-1.3.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5bd82982ff69470b6dfdfdca7f4308cb32c1c6910f95511db58dc59fc0411e59", size = 2508263, upload-time = "2026-07-17T03:22:36.647Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d5/40f52b1892105d33510d0ff472f6743d570e750c33c37d3e9697fde99f94/pillow_jxl_plugin-1.3.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:be5ea80860966bcea91a6e91aaaa3dd510d537cdf5245c811d41cd3a5538f8f1", size = 9730153, upload-time = "2026-07-17T03:23:02.54Z" }, + { url = "https://files.pythonhosted.org/packages/59/ba/e232759c07440d07479973d753a906715000cc71dd56fe7df70fd58304df/pillow_jxl_plugin-1.3.8-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:537a6f702b066bd8c69ba8202f1350a3d1e05e23bbe28f0d5f1567588a5bb16e", size = 8391469, upload-time = "2026-07-17T03:23:15.114Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ab/410e078dddb769bcbf238837add920a788cd063aea43c04fbff29ea9d02b/pillow_jxl_plugin-1.3.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:85d8496e35035e1a228daf52578bde6f94fcd908202324f32dfe3b7b0430efd0", size = 9037205, upload-time = "2026-07-17T03:23:26.945Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a0/3dcd7f6aa750ec4d01f467abb481aacca000f06f67ed1724beac6dcdfff3/pillow_jxl_plugin-1.3.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0a04950cff6e0550574117512b2cf5dc3d084d2abd95c752bd7ab219f5511e79", size = 9237632, upload-time = "2026-07-17T03:23:38.387Z" }, + { url = "https://files.pythonhosted.org/packages/bc/27/3cfca1046155b0c8561d4f68fc12971853c9f3fd25aa9dae5a44544615d7/pillow_jxl_plugin-1.3.8-cp310-cp310-win32.whl", hash = "sha256:812a8f0a24d1bc751bbe4119ead7d458726558b2c1af72f99e6c381cb6d48025", size = 4557913, upload-time = "2026-07-17T03:24:04.921Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fb/339b34a676805d8949350d607bdd0247401cf887c565e8fab03ba2a019ec/pillow_jxl_plugin-1.3.8-cp310-cp310-win_amd64.whl", hash = "sha256:ec20f55bfad78d96376b7274671870311f53ef724e07d9d5af8bba36c363f77f", size = 4942011, upload-time = "2026-07-17T03:23:57.29Z" }, + { url = "https://files.pythonhosted.org/packages/db/46/9591ffeae92e62b70b0a297cf0de858ee69cda369a9a540b8989850f5c38/pillow_jxl_plugin-1.3.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:42bfc37327ca71e9b9c4a3d569d338db6789e0966eb89dd1fffef5fcf3c2462d", size = 2361103, upload-time = "2026-07-17T03:22:55.63Z" }, + { url = "https://files.pythonhosted.org/packages/df/f7/1dc8ddbfa056e39307d67286f45c8300695c9a0137ed98f60775a2c37322/pillow_jxl_plugin-1.3.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ceaca73f77fa0dc6409e85ca77da24560270d0929bdbee579714da05614c67ec", size = 2667692, upload-time = "2026-07-17T03:22:47.217Z" }, + { url = "https://files.pythonhosted.org/packages/a0/27/018351fecb8ebf35103408484dca3072f1a0ff28247193f46377ff6341f0/pillow_jxl_plugin-1.3.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c3109317d54918fe10d25d9978c2e445edefee04546f08ef47e077dc3dbe7e6", size = 2791235, upload-time = "2026-07-17T03:22:20.337Z" }, + { url = "https://files.pythonhosted.org/packages/12/09/cd6dff8524153196150bc778bfb828f44b65e301e06c34a1c7ca39049c49/pillow_jxl_plugin-1.3.8-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9fd3a2c5d48faca23200176015e00b4004862f4fb9016fd17a0fa374019574e", size = 2505571, upload-time = "2026-07-17T03:22:29.564Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/453f3268eb67190930294875628890b8eacd297364a2abadf206d715cdcf/pillow_jxl_plugin-1.3.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97a627c02fc660e6d36010c2bf57f1c346ce2a58cd7e83e43241f5ad174c3f18", size = 2508619, upload-time = "2026-07-17T03:22:38.406Z" }, + { url = "https://files.pythonhosted.org/packages/dc/80/a5859f365485fbc9e741d48c596f3913fa47fb2f4df77a134560d7766380/pillow_jxl_plugin-1.3.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:17fd143e6fe5c2cbb8eac1862046d63ada83ed48b6cea4753b9615a6df6f4d08", size = 9729439, upload-time = "2026-07-17T03:23:04.669Z" }, + { url = "https://files.pythonhosted.org/packages/2b/51/afa9751b8a397ad91e19a5e8afde925511abdf2a70fe98da13f6d77a567b/pillow_jxl_plugin-1.3.8-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a86df5b196225d230c51e391f1005e86e136142d0fce98267df285651f3b0621", size = 8390938, upload-time = "2026-07-17T03:23:17.05Z" }, + { url = "https://files.pythonhosted.org/packages/50/a6/656248d1e5ce08d6154a3c904332a12093c48f22daef3779ed344a449c02/pillow_jxl_plugin-1.3.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bcd116d028fe87379c934238cd3a984325bb4fde80c86526873a513a4d3c3b81", size = 9037333, upload-time = "2026-07-17T03:23:28.992Z" }, + { url = "https://files.pythonhosted.org/packages/34/35/d669758c1b49b04673b95e6bb275d59ed9f901d7a8346c8f560d5b6136fa/pillow_jxl_plugin-1.3.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2f11b1d5d20013a8bdab06d9d3244488db0e00a5d57e5daaefa78f67eb70f474", size = 9237613, upload-time = "2026-07-17T03:23:40.223Z" }, + { url = "https://files.pythonhosted.org/packages/61/af/15ddbdd53a96005c8f048ce1aafab2e825e6445a3565408989e9f37bc237/pillow_jxl_plugin-1.3.8-cp311-cp311-win32.whl", hash = "sha256:d8ff168cbc04a5978f0a9320958e1ac2111e53ba4f7f7ae14a09f49c04b4e936", size = 4556704, upload-time = "2026-07-17T03:24:06.495Z" }, + { url = "https://files.pythonhosted.org/packages/dc/57/ff6381e1df975840a0af201adca7d54bb6d7cd116514527c5179f8f599f5/pillow_jxl_plugin-1.3.8-cp311-cp311-win_amd64.whl", hash = "sha256:85649ffbb6a21b2830022ce723de8625fc4d33621ed09ec4687eaf57b5044f4c", size = 4941456, upload-time = "2026-07-17T03:23:58.841Z" }, + { url = "https://files.pythonhosted.org/packages/6b/16/8c9bad406251c6a7e828641102e9bae75d2e3880b866855a699bf8b6e62b/pillow_jxl_plugin-1.3.8-cp311-cp311-win_arm64.whl", hash = "sha256:d72cbe293981677e989cf4314cb18e743e42ac84269508c0693e06c8f7e66604", size = 2333977, upload-time = "2026-07-17T03:23:51.631Z" }, + { url = "https://files.pythonhosted.org/packages/78/bb/093aced52ebaafcd050eb21ab07ddcab7efffaa687e264f64d3f0423a4ba/pillow_jxl_plugin-1.3.8-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7f6fd0fa12dd0d98bb55df6bbfff7ad913e4ea27306fbdf5e8543fe1fa40772c", size = 2361426, upload-time = "2026-07-17T03:22:56.906Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9f/f272fd205ab511475b135772e85bcaa71e5f3d081e6604b08e6e1d3982a6/pillow_jxl_plugin-1.3.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:01b632dbca07b59f7d6e106dad3ac8391e9ffdab1f94f2068a3e8edff1bbd180", size = 2660368, upload-time = "2026-07-17T03:22:48.663Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/3445d2b4f6d6e4e26f10e9ccd75b60a14295b2d8eb280f0b1bd0bd7caac3/pillow_jxl_plugin-1.3.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6459d131af2cbccab20742f6fe43a3022b021dcd1ee20b05f99b1fb54d0ec525", size = 2788244, upload-time = "2026-07-17T03:22:21.809Z" }, + { url = "https://files.pythonhosted.org/packages/26/f2/3855e83fd1e4043bd7ab286d3f0ca434fbf935598fae57f1af2777620891/pillow_jxl_plugin-1.3.8-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b9a546ac71757f53f0ca2b14ea8f99c33b70e298a7873477f4548e2d83f817", size = 2500550, upload-time = "2026-07-17T03:22:30.993Z" }, + { url = "https://files.pythonhosted.org/packages/f0/13/d9befae0e705474e550b16c90bb6d33c3f07837af63a0e8ce4c84fb546fb/pillow_jxl_plugin-1.3.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd2afd5153e2a7ef230a3e48a679a9e1cb3e59032f79c78bb2f78010a4e9bfc1", size = 2504336, upload-time = "2026-07-17T03:22:40.143Z" }, + { url = "https://files.pythonhosted.org/packages/52/2c/3e671c8dd84f6ed4e47d588df1dc15aae61b0df0302f4bfff7b5c743045b/pillow_jxl_plugin-1.3.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b987fb202dd1c71d7f6e103363afd1128d347ffcf540d8416c257c8f1041ff3", size = 9727025, upload-time = "2026-07-17T03:23:06.554Z" }, + { url = "https://files.pythonhosted.org/packages/27/89/a63b75d9c05deb0017ff51ddf3674cef19dbfe9c2fb74117d7e4a66b9a93/pillow_jxl_plugin-1.3.8-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c05cf62a992f48434a6b876fe010e5cae12bd54039854ce9ae0f05c13a58c7ad", size = 8386443, upload-time = "2026-07-17T03:23:19.195Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4f/b6ead8f5a50103e2c6b264a6d4778b8fcd08006c957b2ad0bccbbb306e97/pillow_jxl_plugin-1.3.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0392e5349b67522130ecff9d8dca4713e4c84662a310d2170291b229965e8fd1", size = 9032022, upload-time = "2026-07-17T03:23:30.993Z" }, + { url = "https://files.pythonhosted.org/packages/ea/53/b9aa7c156427ad4d94d8c90bd87cb502e7d6326d2689c309aa574baf1ab7/pillow_jxl_plugin-1.3.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:88b06a4e5fc0d882b188824990b6571e201ad775d22a09487c8d5ab8aed9c3b7", size = 9233286, upload-time = "2026-07-17T03:23:42.022Z" }, + { url = "https://files.pythonhosted.org/packages/32/e3/b25194909cfef9081f6a1f0e40e699e95ea96b211cae30bbf0f965d6b450/pillow_jxl_plugin-1.3.8-cp312-cp312-win32.whl", hash = "sha256:3345a45a09ba3149001c3e9a1b6991be8aab72c33097c0633e414a254cd283fa", size = 4552597, upload-time = "2026-07-17T03:24:08.129Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c9/01efd23cb4827f8a1731e1a7652023a06375e933f85e3169e0761d916d6f/pillow_jxl_plugin-1.3.8-cp312-cp312-win_amd64.whl", hash = "sha256:5b1c0d7a0c7e451014b288f0504545bff0f543f2ee1581af628e01e885b75f50", size = 4937252, upload-time = "2026-07-17T03:24:00.294Z" }, + { url = "https://files.pythonhosted.org/packages/db/ef/b3aee5725df66399f529c41d97f71893e2b38f665c9278a99519112f03f3/pillow_jxl_plugin-1.3.8-cp312-cp312-win_arm64.whl", hash = "sha256:230a1d086ef5b66055e0dbc9837914becf1d1ca58d45d1404660de0a11fb63c3", size = 2330507, upload-time = "2026-07-17T03:23:53.22Z" }, + { url = "https://files.pythonhosted.org/packages/b9/10/18bb2d6ed82184e2427f7501a17c1b413eb50bdcd4ebb42bbfb187bb4290/pillow_jxl_plugin-1.3.8-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:fe24e957016dcb0c2ec35f72ff3bb8ae78a9468b282f0d423485556de492975c", size = 2361184, upload-time = "2026-07-17T03:22:58.376Z" }, + { url = "https://files.pythonhosted.org/packages/89/3e/5e1156e5c5b402d84ef924153e046ba937ac46e8b14decb3ab6e01a6f40e/pillow_jxl_plugin-1.3.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d648ef965002bee0c98dbf144b9b3c994146856fa4d3c54008b75dda10f69915", size = 2659163, upload-time = "2026-07-17T03:22:50.003Z" }, + { url = "https://files.pythonhosted.org/packages/e9/fe/e610196142343f80c98502fd204f28e901a1101db6988be6ccc40c0e8ca0/pillow_jxl_plugin-1.3.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34685b27d01e55aabb09d9436d4ddb1f446e8805da82ffd6448c07bbf563354c", size = 2787568, upload-time = "2026-07-17T03:22:23.469Z" }, + { url = "https://files.pythonhosted.org/packages/6e/18/a862b140f82be55ce62c681b63c38bf61c962f0c27e32797ebebc0d47548/pillow_jxl_plugin-1.3.8-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:288eb07090c91891ee0dc782786d937af8e53a117835606d7982c29e31072b90", size = 2499957, upload-time = "2026-07-17T03:22:32.414Z" }, + { url = "https://files.pythonhosted.org/packages/59/29/d430cd5f82c08d2950e292d195081e9dd4e2a038a7e1722d79ba7c6e7ed9/pillow_jxl_plugin-1.3.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b39ac44cbe5a98b8a364ed98a16c8c1e197ee997e32731be4be727c55e629e4", size = 2504367, upload-time = "2026-07-17T03:22:41.501Z" }, + { url = "https://files.pythonhosted.org/packages/f4/68/6806df96fdd625943c795beca932ac033c255a323b63b051903df96935b6/pillow_jxl_plugin-1.3.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0e1550f30768a0aa3f756da10b7807f6fac7fd2e5a18991eb8e00ec5908089d8", size = 9726010, upload-time = "2026-07-17T03:23:08.579Z" }, + { url = "https://files.pythonhosted.org/packages/12/df/d12f42d6e31bb47e2002fcba2db9d92aad19dc3db9ae7d4b2b76152e268a/pillow_jxl_plugin-1.3.8-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b13f860c280d9d5770f49a1909926f15c6e26e50935da52ecb78700f7c377857", size = 8386305, upload-time = "2026-07-17T03:23:20.983Z" }, + { url = "https://files.pythonhosted.org/packages/92/ee/4dc11ad5a90aa6d2eeae6eb0be2376cde0c365423036a078746b3476eca2/pillow_jxl_plugin-1.3.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:273908ffb85f68a2374b0934700939c572f32b355c9d128d4e3b6924e72ef274", size = 9031514, upload-time = "2026-07-17T03:23:32.948Z" }, + { url = "https://files.pythonhosted.org/packages/ae/76/b3125f13c9551862ad3ae7137d86959cde159b759083f3e7b5e720525fd0/pillow_jxl_plugin-1.3.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2be721646718af722f2ca5c1d6b06bf056a61a0db355cf944df53c3723119d4b", size = 9233009, upload-time = "2026-07-17T03:23:44.276Z" }, + { url = "https://files.pythonhosted.org/packages/13/16/1aa669ae22a6c36810a925bbbfb1f4521df1d0402180052331b39ef8dee0/pillow_jxl_plugin-1.3.8-cp313-cp313-win32.whl", hash = "sha256:a2716f1367ed9647da36ce15882a12b4d424892cb5a03ab4b0783039d701c7c2", size = 4551381, upload-time = "2026-07-17T03:24:09.552Z" }, + { url = "https://files.pythonhosted.org/packages/0e/e7/823f58aaea0d969f876f593a39c85c39fefe1b8ee9ca10a6ffbafe1a254a/pillow_jxl_plugin-1.3.8-cp313-cp313-win_amd64.whl", hash = "sha256:2388374b3493df9add4ef3d97cc70d6a5f51ed188dfe10d2b83ebc55e82e48d4", size = 4936720, upload-time = "2026-07-17T03:24:01.9Z" }, + { url = "https://files.pythonhosted.org/packages/3e/12/e04b73832d1b8af3c5c4646127949a362f9ca3d30f42b05156ca5ffc61ea/pillow_jxl_plugin-1.3.8-cp313-cp313-win_arm64.whl", hash = "sha256:48d13d81d1c4933e5d42dd09366a9827583808be0a2fe842abd7358653c7d92f", size = 2330087, upload-time = "2026-07-17T03:23:54.604Z" }, + { url = "https://files.pythonhosted.org/packages/66/30/c37d161345302ebda07f29d8230b15ba83b43c586f14fec98d088b18e982/pillow_jxl_plugin-1.3.8-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:30b72accb8505a46b05f709871da40f09975aea3a6b6f3cb1d646ff0a9aaba5e", size = 2362140, upload-time = "2026-07-17T03:22:59.694Z" }, + { url = "https://files.pythonhosted.org/packages/a7/be/01613af0307c31e77aced40bdf9cf3d25ce1333b3e99bb85575bdcbfeca5/pillow_jxl_plugin-1.3.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b5bbc933487207e634802f21990c365b6e61473cea34f6621420f4eda8ffd1df", size = 2660381, upload-time = "2026-07-17T03:22:51.351Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8b/89636d315a31eb805681e60e569fe38d5eef3d09f13b1b6f576574387942/pillow_jxl_plugin-1.3.8-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1d06fd4eb038f431db27f1ab2e483fd6967bafb21a7757e1bd5f76b6fd93649", size = 2787875, upload-time = "2026-07-17T03:22:24.736Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e0/7b9bdc388d89773a5f9ae74ff0be3b06080ef1a60be57b5897d929012622/pillow_jxl_plugin-1.3.8-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f65361ce18db59e8949c60599bfa46b7de187e28917fc39b5d816ab4b81cb16d", size = 2500834, upload-time = "2026-07-17T03:22:33.928Z" }, + { url = "https://files.pythonhosted.org/packages/24/25/0903ba5a4c8d948df9217e48182ff7d872098ddf86fe512402569f4041cc/pillow_jxl_plugin-1.3.8-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b881a40737c647a24270b73cd3f506dd16d634202e5214cb492f9270bd1b02", size = 2504805, upload-time = "2026-07-17T03:22:42.911Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ef/77a15b2f3227854d4ef2563250ea57b03538ed962f406f1248c9caf045aa/pillow_jxl_plugin-1.3.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4049e48f09947a38047fc5d70817c6dffa90ffb5f14585c8c00e8b534d17c9b", size = 9725768, upload-time = "2026-07-17T03:23:10.845Z" }, + { url = "https://files.pythonhosted.org/packages/c6/95/486fc1fabd1e6cb8b520dce9898b51f2e29e991ba54e632381fe7f287300/pillow_jxl_plugin-1.3.8-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6ce20ef0887efb6dace5245f714f94b4f070ef39a1ec45161b35879c48faf30a", size = 8386447, upload-time = "2026-07-17T03:23:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/05/28/7979927eb5dc79e40b31e4e07d074057722656743d324182f4a68056c2a9/pillow_jxl_plugin-1.3.8-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:845e61add39272ca8545f4a0e6ead977ebb7e89fa017014bb8e17273a7a09c6c", size = 9032664, upload-time = "2026-07-17T03:23:34.78Z" }, + { url = "https://files.pythonhosted.org/packages/92/b7/f606ed6fcb5fd9e177ce2b15a613a405ac22ceafc2c03bb3f58cd500ca04/pillow_jxl_plugin-1.3.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:915b8fee6a97aaf5486d0d625f26faae2e9f0d9b5892f1076f5a29b8103125a4", size = 9233868, upload-time = "2026-07-17T03:23:46.277Z" }, + { url = "https://files.pythonhosted.org/packages/a4/6d/d10a4426fc3b394d2222cb694774451e67ab052df2e15a488dfac22be561/pillow_jxl_plugin-1.3.8-cp314-cp314-win32.whl", hash = "sha256:d63d1c3566a8c9ebdd1ec6b75da816b9475d95c4f69656827d6d3957d2594ecb", size = 4554691, upload-time = "2026-07-17T03:24:11.39Z" }, + { url = "https://files.pythonhosted.org/packages/47/77/0f0297c265f1743784b6d1df719e62e49dabd1d9ede8efb0a9ba93174293/pillow_jxl_plugin-1.3.8-cp314-cp314-win_amd64.whl", hash = "sha256:6b23c83aabf4615b92ae609293d8597ca1a8d14d01ab16c703bb3a3df359796b", size = 4939714, upload-time = "2026-07-17T03:24:03.288Z" }, + { url = "https://files.pythonhosted.org/packages/ca/cf/2db398fb1a41c46fd382bad96ee90283589eaed0ec2c50cfe4afda3df72c/pillow_jxl_plugin-1.3.8-cp314-cp314-win_arm64.whl", hash = "sha256:60c36bec3d8d4ef23cf2aed0f792213f999312dd3d8cd5225ae1494c430e16f6", size = 2330667, upload-time = "2026-07-17T03:23:55.913Z" }, + { url = "https://files.pythonhosted.org/packages/58/a6/2e08df50151b3a399ee18b4c8987e25dd754547060d1a368cda4edb2513d/pillow_jxl_plugin-1.3.8-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e33d2b965d4306271ca811dfbb14f8dc313e7bc6408617760fae7ae275686218", size = 2362588, upload-time = "2026-07-17T03:23:01.11Z" }, + { url = "https://files.pythonhosted.org/packages/72/3e/70b1c9c18839b7c6980edb6f4e0c4dd6b52be96df380adf2da55be8e6f7d/pillow_jxl_plugin-1.3.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:323ba7ab8fe6aceddbf571853b47c8900a293c6430649be31c3a743b12d0bcfc", size = 2668471, upload-time = "2026-07-17T03:22:52.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/6f/295bd97058531eee2d3e3a0c73795c95aace572c918d13688201d9f45abd/pillow_jxl_plugin-1.3.8-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66a1bbe948021af26a5550b9bccad011e1131f6e10a90821690e3c19a417f59b", size = 2793160, upload-time = "2026-07-17T03:22:26.35Z" }, + { url = "https://files.pythonhosted.org/packages/3a/62/74ae963d945cf8c0da64a1a5b1a8e32b49cb1ac8cf3f81e1b63c25466250/pillow_jxl_plugin-1.3.8-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9adee3403a29b42b203b6903923ef84cff70bb33aa6ed069775b6b8f721fe7c7", size = 2506776, upload-time = "2026-07-17T03:22:35.262Z" }, + { url = "https://files.pythonhosted.org/packages/9d/4f/0f00dab8c1b4b026cb62909e01b26a8f12f9f536bb6b002c71a15a638004/pillow_jxl_plugin-1.3.8-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40c82ead318b058804211a7f6d6e2715f65015f73792dee63813731105f74c", size = 2509592, upload-time = "2026-07-17T03:22:44.31Z" }, + { url = "https://files.pythonhosted.org/packages/53/f6/1ea001b109ff39165e013e663822adba393340d1599e165dbc62f68772a8/pillow_jxl_plugin-1.3.8-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:81864c18c03084754c4a9d673a0ec9d6161af5f7695ab1d4bb112059af2e968b", size = 9732562, upload-time = "2026-07-17T03:23:13.06Z" }, + { url = "https://files.pythonhosted.org/packages/d0/87/1ea5bb4cd84c5be48b542cbb579f43932c7977f5317d9992d674f409377b/pillow_jxl_plugin-1.3.8-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:42f39d55dfb7ba29509cea3f0270bd0e8a2fb924d979f2caa43f445e30d709e5", size = 8392645, upload-time = "2026-07-17T03:23:25.036Z" }, + { url = "https://files.pythonhosted.org/packages/0a/45/1e39b708d0260eb04aeb672558522bea63df4f7df3a9c27710faef0736b6/pillow_jxl_plugin-1.3.8-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:027f091b8ff8ecb26712d709847d25359994eb6a6543736d7fbc07b7b6b86fd7", size = 9039036, upload-time = "2026-07-17T03:23:36.555Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d9/c54bce2efd22f3d79e83fa9948fa5ca0184422c6bd9c38c4d28861ee9a50/pillow_jxl_plugin-1.3.8-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:75351073d967c85c24a330f9e375b17b809bff9779cd08c8d0e90d659b39be5a", size = 9239329, upload-time = "2026-07-17T03:23:48.617Z" }, +] + [[package]] name = "platformdirs" version = "4.11.0" From 5e730f246e060859fcc7ae6d3617e35b4d0ba067 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Mon, 27 Jul 2026 23:45:22 -0700 Subject: [PATCH 2/5] Gate WebP to JXL conversion behind --convert-webp-to-jxl JPEG XL support in browsers and image tooling is still thin, so converting away from WebP, which every browser reads, should be a deliberate act rather than a side effect of asking for -c JXL. JPEG and WEBP are both formats picopt processes by default, which means the usual "name the source with -x" gate cannot express a choice about them: -x JPEG and -x WEBP are no-ops. Each therefore carries its own flag, and a plain -c JXL once again converts only GIF and PNG, as the README has always described. The previous commit had quietly made WEBP a third convert-by-default source. The flag hangs off the handler class, and JxlLossless also serves PNG, GIF and native JXL, which must stay ungated. So the WebP route now points at a JxlFromWebP subclass that exists only to carry CONFIG_ENABLED_KEY. Converting out of JXL into a more widely readable format stays ungated. Co-Authored-By: Claude Fable 5 --- NEWS.md | 10 +++++--- README.md | 17 ++++++++++---- picopt/cli.py | 11 +++++++++ picopt/config/__init__.py | 2 ++ picopt/config/consts.py | 5 ++-- picopt/config/settings.py | 1 + picopt/config_default.yaml | 1 + picopt/plugins/jxl.py | 45 +++++++++++++++++++++++++++--------- tests/test_atomic_replace.py | 1 + tests/test_config_cli.py | 16 ++++++++++--- tests/test_jxl.py | 44 ++++++++++++++++++++++++++++++++++- tests/test_timestamps.py | 1 + 12 files changed, 129 insertions(+), 25 deletions(-) diff --git a/NEWS.md b/NEWS.md index a4dc410b..b0e04ea4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -13,9 +13,13 @@ order for still images ahead of WEBP and PNG. - New `--convert-jpeg-to-jxl` converts JPEG to JXL losslessly and reversibly: the JXL stores the original JPEG bitstream and can restore it byte for byte. - It requires `-c JXL` as well, because picopt otherwise never converts from a - lossy source. Files carrying that reconstruction data are never re-encoded, so - the original JPEG stays recoverable. + Files carrying that reconstruction data are never re-encoded, so the original + JPEG stays recoverable. +- New `--convert-webp-to-jxl` converts lossless WebP to JXL. Off by default + because JXL support is still much thinner than WebP's. +- Both flags require `-c JXL` as well. JPEG and WEBP are formats picopt already + processes by default, so unlike BMP or TIFF they cannot be gated by naming + them with `-x`. A plain `-c JXL` still converts only GIF and PNG. ## v6.6.3 diff --git a/README.md b/README.md index 94672a6f..c2273f93 100644 --- a/README.md +++ b/README.md @@ -101,17 +101,24 @@ Lossless JXL images are re-encoded at the highest size-targeting effort. Lossy Converting JPEG to JPEG XL is lossless and reversible: JPEG XL stores the original JPEG bitstream, so the exact original file can be restored later. -Because picopt otherwise never uses a lossy image as a conversion source, this -one needs its own flag in addition to the conversion target: + +JPEG and WebP are both formats picopt processes by default, so naming them with +`-x` cannot express a choice about them, and each has its own flag instead: ```sh -picopt -c JXL --convert-jpeg-to-jxl photos +picopt -c JXL --convert-jpeg-to-jxl --convert-webp-to-jxl photos ``` -Two caveats. `--strip-metadata` cannot apply to this conversion, since keeping -the JPEG restorable means keeping all of its bytes. And picopt will not +JPEG needs one because picopt otherwise never uses a lossy image as a conversion +source. WebP needs one because JPEG XL is still far less widely supported than +WebP, and converting away from a format every browser reads should be a +deliberate act. Without them, `-c JXL` converts only GIF and PNG, plus whatever +else you name with `-x`. + +Two caveats on the JPEG conversion. `--strip-metadata` cannot apply to it, since +keeping the JPEG restorable means keeping all of its bytes. And picopt will not re-optimize a JXL that carries JPEG reconstruction data, because re-encoding it would silently discard the ability to restore the original JPEG. diff --git a/picopt/cli.py b/picopt/cli.py index 4e3c784a..c2ad8d25 100644 --- a/picopt/cli.py +++ b/picopt/cli.py @@ -279,6 +279,17 @@ def get_arguments(params: tuple[str, ...] | None = None) -> Namespace: "from the JXL byte for byte. Requires -c JXL." ), ) + parser.add_argument( + "--convert-webp-to-jxl", + action="store_true", + default=None, + dest="convert_webp_to_jxl", + help=( + "Also convert lossless WebP images when converting to JXL. Off " + "by default because JXL is still far less widely supported than " + "WebP. Requires -c JXL." + ), + ) parser.add_argument( "-S", "--no-symlinks", diff --git a/picopt/config/__init__.py b/picopt/config/__init__.py index ed432d03..6801f52b 100644 --- a/picopt/config/__init__.py +++ b/picopt/config/__init__.py @@ -89,6 +89,7 @@ def _build_template() -> MappingTemplate: "bigger": bool, "convert_jpeg_to_jxl": bool, "convert_to": Optional(Sequence(Choice(convert_to_format_strs))), + "convert_webp_to_jxl": bool, "disable_programs": Sequence(str), "dry_run": bool, "extra_formats": Optional(Sequence(Choice(all_format_strs))), @@ -487,6 +488,7 @@ def _settings_from_attrdict(ad: Any) -> PicoptSettings: bigger=ad.bigger, convert_jpeg_to_jxl=ad.convert_jpeg_to_jxl, convert_to=tuple(ad.convert_to) if ad.convert_to is not None else None, + convert_webp_to_jxl=ad.convert_webp_to_jxl, disable_programs=tuple(ad.disable_programs), dry_run=ad.dry_run, extra_formats=tuple(ad.extra_formats) if ad.extra_formats is not None else None, diff --git a/picopt/config/consts.py b/picopt/config/consts.py index e4d499c8..40d5e96e 100644 --- a/picopt/config/consts.py +++ b/picopt/config/consts.py @@ -19,10 +19,11 @@ TIMESTAMPS_CONFIG_KEYS: Final[frozenset[str]] = frozenset( { "bigger", - # Changes which files are converted, so it must invalidate stamps - # for the same reason convert_to does. + # These change which files are converted, so they must invalidate + # stamps for the same reason convert_to does. "convert_jpeg_to_jxl", "convert_to", + "convert_webp_to_jxl", "formats", "ignore", "keep_metadata", diff --git a/picopt/config/settings.py b/picopt/config/settings.py index b21cecc0..7b2a161b 100644 --- a/picopt/config/settings.py +++ b/picopt/config/settings.py @@ -71,6 +71,7 @@ class PicoptSettings: # User-facing scalar options bigger: bool convert_jpeg_to_jxl: bool + convert_webp_to_jxl: bool dry_run: bool fail_fast: bool fail_fast_container: bool diff --git a/picopt/config_default.yaml b/picopt/config_default.yaml index 94274828..971d6e1a 100644 --- a/picopt/config_default.yaml +++ b/picopt/config_default.yaml @@ -3,6 +3,7 @@ picopt: bigger: False convert_jpeg_to_jxl: False convert_to: [] + convert_webp_to_jxl: False disable_programs: [] dry_run: False fail_fast: False diff --git a/picopt/plugins/jxl.py b/picopt/plugins/jxl.py index af3a6212..1aec4d47 100644 --- a/picopt/plugins/jxl.py +++ b/picopt/plugins/jxl.py @@ -2,7 +2,7 @@ JPEG XL format plugin. Owns: JXL (still). Tool: the ``pillow-jxl-plugin`` Pillow codec, driven -in-process. Two handlers with different jobs: +in-process. Three handlers with different jobs: * :class:`JxlLossless` re-encodes lossless JXL in place and is the target for every lossless still conversion. Only files that @@ -10,9 +10,16 @@ (XYB-encoded) JXL is left alone exactly as lossy WebP is. * :class:`JxlFromJpeg` converts JPEG to JXL by lossless bitstream reconstruction, which is reversible: the original JPEG can be restored - byte for byte. It is doubly opt-in — ``--convert-to JXL`` *and* - ``--convert-jpeg-to-jxl`` — because picopt otherwise never uses a lossy - format as a conversion source. + byte for byte. +* :class:`JxlFromWebP` converts lossless WebP to JXL. + +The last two exist only to carry their own opt-in flag, so each is doubly +gated: ``--convert-to JXL`` *and* its own option. JPEG needs that because +picopt otherwise never uses a lossy format as a conversion source. WebP +needs it because JXL support in the wider world is still thin, and +converting away from a format every browser reads deserves to be +deliberate. Both are formats picopt processes by default, so the usual +"name the source with ``-x``" gate cannot express the choice. Animation is deliberately absent: the codec can neither read nor write animated JXL, so no animated route points here in either direction. @@ -204,28 +211,44 @@ class JxlFromJpeg(ImageHandler): PIPELINE: tuple[tuple[Tool, ...], ...] = ((JpegXlReconstructTool(),),) +class JxlFromWebP(JxlLossless): + """ + Lossless WebP to JXL, gated behind its own option. + + Identical to its parent in every respect but the gate. It is a separate + class only because the flag hangs off the handler, and JxlLossless also + serves PNG, GIF and native JXL, which must stay ungated. + """ + + INPUT_FILE_FORMATS = frozenset({WebPLossless.OUTPUT_FILE_FORMAT}) + CONFIG_ENABLED_KEY = "convert_webp_to_jxl" + + # --------------------------------------------------------------------------- # Plugin descriptor # --------------------------------------------------------------------------- PLUGIN = Plugin( name="JXL", - handlers=(JxlLossless, JxlFromJpeg), + handlers=(JxlLossless, JxlFromJpeg, JxlFromWebP), routes=( - # Native, plus the reverse conversions out of JXL. + # Native, plus the reverse conversions out of JXL. Converting away + # from JXL to a more widely readable format is never gated. Route( file_format=JxlLossless.OUTPUT_FILE_FORMAT, native=JxlLossless, convert=(WebPLossless, Png), ), - # Lossless still sources. Formats owned by pil_convertible (BMP, - # TIFF, PNM, …) get JXL added to their convert chain there. + # Lossless still sources that convert on a plain -c JXL. Formats + # owned by pil_convertible (BMP, TIFF, PNM, …) get JXL added to + # their convert chain there, and still need their own -x. Route(file_format=Png.OUTPUT_FILE_FORMAT, convert=(JxlLossless,)), Route(file_format=Gif.OUTPUT_FILE_FORMAT, convert=(JxlLossless,)), - Route(file_format=WebPLossless.OUTPUT_FILE_FORMAT, convert=(JxlLossless,)), - # The one lossy source picopt will convert, and only on request. + # Sources picopt processes by default, so -x cannot gate them and + # each carries its own opt-in flag instead. Route(file_format=Jpeg.OUTPUT_FILE_FORMAT, convert=(JxlFromJpeg,)), + Route(file_format=WebPLossless.OUTPUT_FILE_FORMAT, convert=(JxlFromWebP,)), ), - convert_targets=(JxlLossless, JxlFromJpeg), + convert_targets=(JxlLossless, JxlFromJpeg, JxlFromWebP), default_enabled=True, ) diff --git a/tests/test_atomic_replace.py b/tests/test_atomic_replace.py index cba0be3f..84360be7 100644 --- a/tests/test_atomic_replace.py +++ b/tests/test_atomic_replace.py @@ -51,6 +51,7 @@ def _make_settings(*, dry_run: bool = False, preserve: bool = False) -> PicoptSe return PicoptSettings( bigger=False, convert_jpeg_to_jxl=False, + convert_webp_to_jxl=False, dry_run=dry_run, fail_fast=False, fail_fast_container=False, diff --git a/tests/test_config_cli.py b/tests/test_config_cli.py index 479b1b3c..6e7c9986 100644 --- a/tests/test_config_cli.py +++ b/tests/test_config_cli.py @@ -11,7 +11,7 @@ from picopt.config import PicoptConfig from picopt.doctor import PicoptDoctor from picopt.plugins.base.tool import Tool, ToolStatus -from picopt.plugins.jxl import JxlFromJpeg, JxlLossless +from picopt.plugins.jxl import JxlFromJpeg, JxlFromWebP, JxlLossless __all__ = () @@ -81,17 +81,27 @@ def test_lowercase_jxl_convert_to_accepted(self) -> None: class TestHandlerConfigGate: """A handler declaring CONFIG_ENABLED_KEY only runs when the flag is on.""" - def test_jpeg_to_jxl_handler_absent_by_default(self) -> None: + def test_gated_handlers_absent_by_default(self) -> None: settings = _get_settings("-c", "JXL") assert JxlFromJpeg.CONFIG_ENABLED_KEY == "convert_jpeg_to_jxl" + assert JxlFromWebP.CONFIG_ENABLED_KEY == "convert_webp_to_jxl" assert JxlFromJpeg not in settings.computed.handler_stages - # The unconditional handler alongside it is still available. + assert JxlFromWebP not in settings.computed.handler_stages + # The unconditional handler alongside them is still available. assert JxlLossless in settings.computed.handler_stages def test_jpeg_to_jxl_handler_present_when_enabled(self) -> None: settings = _get_settings("-c", "JXL", "--convert-jpeg-to-jxl") assert settings.convert_jpeg_to_jxl is True assert JxlFromJpeg in settings.computed.handler_stages + # Each flag gates only its own handler. + assert JxlFromWebP not in settings.computed.handler_stages + + def test_webp_to_jxl_handler_present_when_enabled(self) -> None: + settings = _get_settings("-c", "JXL", "--convert-webp-to-jxl") + assert settings.convert_webp_to_jxl is True + assert JxlFromWebP in settings.computed.handler_stages + assert JxlFromJpeg not in settings.computed.handler_stages class _FakeTool(Tool): diff --git a/tests/test_jxl.py b/tests/test_jxl.py index 20833996..335b7751 100644 --- a/tests/test_jxl.py +++ b/tests/test_jxl.py @@ -25,13 +25,16 @@ # Lossless still sources convert. "test_png.png": (7967, 4152, ("jxl", 3927)), "test_gif.gif": (138952, 138944, ("jxl", 115204)), - # A JPEG must NOT convert without the extra opt-in. + # Neither of these convert without their own opt-in flag. "test_jpg.jpg": (97373, 87913, ("jpg", 87913)), + "test_webp_lossless.webp": (5334, 3870, ("webp", 3870)), } ) JPEG_SRC = "test_jpg.jpg" JPEG_JXL_SIZE = 78762 +WEBP_SRC = "test_webp_lossless.webp" +WEBP_JXL_SIZE = 3901 @pytest.mark.parametrize("fn", FNS) @@ -119,6 +122,45 @@ def test_reconstruction_jxl_is_never_reencoded( assert has_reconstruction +@pytest.mark.parametrize("fn", [WEBP_SRC]) +class TestJxlFromWebP(BaseTest): + """Test the opt-in lossless WebP to JXL conversion.""" + + TMP_ROOT: Path = get_test_dir() + SOURCE_DIR: Path = IMAGES_DIR + FNS: MappingProxyType[str, tuple] = FNS + + def test_convert_webp_to_jxl(self: "TestJxlFromWebP", fn: str) -> None: + """With the flag, a lossless WebP becomes a JXL.""" + args = ( + PROGRAM_NAME, + "-rvv", + "-c", + "JXL", + "--convert-webp-to-jxl", + str(self.TMP_ROOT), + ) + cli.main(args) + path = (self.TMP_ROOT / fn).with_suffix(".jxl") + assert path.exists() + assert not (self.TMP_ROOT / fn).exists() + assert_size_close(path.stat().st_size, WEBP_JXL_SIZE) + + def test_jpeg_flag_does_not_enable_webp(self: "TestJxlFromWebP", fn: str) -> None: + """Each flag gates only its own source format.""" + args = ( + PROGRAM_NAME, + "-rvv", + "-c", + "JXL", + "--convert-jpeg-to-jxl", + str(self.TMP_ROOT), + ) + cli.main(args) + assert (self.TMP_ROOT / fn).exists() + assert not (self.TMP_ROOT / fn).with_suffix(".jxl").exists() + + EXTRA_FNS = MappingProxyType( { "test_bmp.bmp": (141430, 141430, ("jxl", 40804)), diff --git a/tests/test_timestamps.py b/tests/test_timestamps.py index f9bf7241..fc290193 100644 --- a/tests/test_timestamps.py +++ b/tests/test_timestamps.py @@ -28,6 +28,7 @@ "bigger": False, "convert_jpeg_to_jxl": False, "convert_to": [], + "convert_webp_to_jxl": False, "formats": ["GIF", "JPEG", "JXL", "PNG", "SVG", "WEBP"], "keep_metadata": True, "ignore": [], From b8c979278248e201b7fa8e1351c066961d7b0130 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Tue, 28 Jul 2026 00:06:47 -0700 Subject: [PATCH 3/5] update deps --- bun.lock | 30 ++++++++++++++-------------- package.json | 2 +- uv.lock | 56 ++++++++++++++++++++++++++-------------------------- 3 files changed, 44 insertions(+), 44 deletions(-) diff --git a/bun.lock b/bun.lock index a7d1afb5..845716ab 100644 --- a/bun.lock +++ b/bun.lock @@ -7,7 +7,7 @@ "@eslint-community/eslint-plugin-eslint-comments": "^4.7.2", "@eslint/js": "^10.0.1", "@eslint/json": "^2.0.1", - "@fsouza/prettierd": "^0.28.0", + "@fsouza/prettierd": "^0.29.0", "@prettier/plugin-xml": "^3.4.2", "@stylistic/eslint-plugin": "^5.10.0", "eslint": "^10.8.0", @@ -55,13 +55,13 @@ "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + "@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.4", "", {}, "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg=="], - "@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], + "@babel/parser": ["@babel/parser@8.0.4", "", { "dependencies": { "@babel/types": "^8.0.4" }, "bin": "./bin/babel-parser.js" }, "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g=="], - "@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + "@babel/types": ["@babel/types@8.0.4", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.4" } }, "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g=="], "@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], @@ -91,7 +91,7 @@ "@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.2", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A=="], - "@fsouza/prettierd": ["@fsouza/prettierd@0.28.0", "", { "dependencies": { "core_d": "^6.1.1", "prettier": "^3.8.3" }, "optionalDependencies": { "@babel/parser": "^7.29.7", "@typescript-eslint/typescript-estree": "^8.60.0" }, "bin": { "prettierd": "bin/prettierd" } }, "sha512-XkRkIp2en2Lcm7Qc+zqNm8ClI3RArLCBNG2A4M2xzTw43S/vYSEDQ4NcfW+6ZsUyQeoVqrFcMU5H1yAKCVkv6A=="], + "@fsouza/prettierd": ["@fsouza/prettierd@0.29.0", "", { "dependencies": { "core_d": "^6.1.1", "prettier": "^3.9.5" }, "optionalDependencies": { "@babel/parser": "^8.0.4", "@typescript-eslint/typescript-estree": "^8.64.0" }, "bin": { "prettierd": "bin/prettierd" } }, "sha512-UMBNFAVAP2Vfd8RrsH4bzchtyvqfzFp3COBYSTYmtrrU6fwK+JQLz9s6g9JNRVY6MDBWkUWNpPO63vPD/6KTFg=="], "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], @@ -165,19 +165,19 @@ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.60.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.60.0", "@typescript-eslint/types": "^8.60.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.65.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.65.0", "@typescript-eslint/types": "^8.65.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q=="], "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.62.0", "", { "dependencies": { "@typescript-eslint/types": "8.62.0", "@typescript-eslint/visitor-keys": "8.62.0" } }, "sha512-1lX38kNxXIRb8mEc3lbq5mdHq1Pf2+U0nFU65KfT18mtPxxl0fvjuEE92mHuXPuCtElJhOrddOpyMlM3Z0umEA=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.60.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.65.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg=="], "@typescript-eslint/types": ["@typescript-eslint/types@8.58.1", "", {}, "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.60.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.60.0", "@typescript-eslint/tsconfig-utils": "8.60.0", "@typescript-eslint/types": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.65.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.65.0", "@typescript-eslint/tsconfig-utils": "8.65.0", "@typescript-eslint/types": "8.65.0", "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg=="], "@typescript-eslint/utils": ["@typescript-eslint/utils@8.62.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.62.0", "@typescript-eslint/types": "8.62.0", "@typescript-eslint/typescript-estree": "8.62.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-82r66fi9zYwZ+mTq3vKgwjbZ1PVk/DJzrXFLpG6RnBbdvH8TEGVHIs9H4d2drhkOzf0syZuD/OZvvlu6GDbP4g=="], - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg=="], + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.65.0", "", { "dependencies": { "@typescript-eslint/types": "8.65.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A=="], "@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.11.1", "", { "os": "android", "cpu": "arm" }, "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw=="], @@ -1085,8 +1085,6 @@ "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - "@fsouza/prettierd/prettier": ["prettier@3.8.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw=="], - "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "@npmcli/config/proc-log": ["proc-log@4.2.0", "", {}, "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA=="], @@ -1105,19 +1103,21 @@ "@npmcli/promise-spawn/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], - "@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.60.0", "", {}, "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA=="], + "@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.65.0", "", {}, "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg=="], "@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.62.0", "", {}, "sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg=="], "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.62.0", "", { "dependencies": { "@typescript-eslint/types": "8.62.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ=="], - "@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.60.0", "", {}, "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA=="], + "@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.65.0", "", {}, "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg=="], + + "@typescript-eslint/typescript-estree/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], "@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.62.0", "", {}, "sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg=="], "@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.62.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.62.0", "@typescript-eslint/tsconfig-utils": "8.62.0", "@typescript-eslint/types": "8.62.0", "@typescript-eslint/visitor-keys": "8.62.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A=="], - "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.60.0", "", {}, "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA=="], + "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.65.0", "", {}, "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg=="], "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], diff --git a/package.json b/package.json index 53adf0ee..4ddff532 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@eslint-community/eslint-plugin-eslint-comments": "^4.7.2", "@eslint/js": "^10.0.1", "@eslint/json": "^2.0.1", - "@fsouza/prettierd": "^0.28.0", + "@fsouza/prettierd": "^0.29.0", "@prettier/plugin-xml": "^3.4.2", "@stylistic/eslint-plugin": "^5.10.0", "eslint": "^10.8.0", diff --git a/uv.lock b/uv.lock index 0783a30f..2be090d6 100644 --- a/uv.lock +++ b/uv.lock @@ -171,15 +171,15 @@ wheels = [ [[package]] name = "backrefs" -version = "7.0" +version = "8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/a7dd63622beef68cc0d3c3c36d472e143dd95443d5ebf14cd1a5b4dfbf11/backrefs-7.0.tar.gz", hash = "sha256:4989bb9e1e99eb23647c7160ed51fb21d0b41b5d200f2d3017da41e023097e82", size = 7012453, upload-time = "2026-04-28T16:28:04.215Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/56/4744bcd0c82184e80c52b0ac4076c261a8ffa1f1b343ff2f6e89ce0e1cef/backrefs-8.0.tar.gz", hash = "sha256:b556cd7d36c3a3a2f256b89590b176b8eddfb73bcfaee3a3ddd84ea66d21ce50", size = 7013081, upload-time = "2026-07-26T19:54:24.638Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/39/39a31d7eae729ea14ed10c3ccef79371197177b9355a86cb3525709e8502/backrefs-7.0-py310-none-any.whl", hash = "sha256:b57cd227ea556b0aed3dc9b8da4628db4eabc0402c6d7fcfc69283a93955f7e9", size = 380824, upload-time = "2026-04-28T16:27:55.647Z" }, - { url = "https://files.pythonhosted.org/packages/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-none-any.whl", hash = "sha256:a0fa7360c63509e9e077e174ef4e6d3c21c8db94189b9d957289ae6d794b9475", size = 392626, upload-time = "2026-04-28T16:27:57.42Z" }, - { url = "https://files.pythonhosted.org/packages/36/da/87912ddec6e06feffbaa3d7aa18fc6352bee2e8f1fee185d7d1690f8f4e8/backrefs-7.0-py312-none-any.whl", hash = "sha256:ca42ce6a49ace3d75684dfa9937f3373902a63284ecb385ce36d15e5dcb41c12", size = 398537, upload-time = "2026-04-28T16:27:58.913Z" }, - { url = "https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl", hash = "sha256:f2c52955d631b9e1ac4cd56209f0a3a946d592b98e7790e77699339ae01c102a", size = 400491, upload-time = "2026-04-28T16:28:00.928Z" }, - { url = "https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-none-any.whl", hash = "sha256:a6448b28180e3ca01134c9cf09dcebafad8531072e09903c5451748a05f24bc9", size = 412349, upload-time = "2026-04-28T16:28:02.412Z" }, + { url = "https://files.pythonhosted.org/packages/e3/fd/9bf53b6a6f6f519ffaac765df2f2a25e5c2fc6d32cfd2b2747099e72c911/backrefs-8.0-py310-none-any.whl", hash = "sha256:4a627b817fd2dce43b79ab48da63613340509381cd8ce0897078a0bce79a2ab8", size = 380377, upload-time = "2026-07-26T19:54:17.457Z" }, + { url = "https://files.pythonhosted.org/packages/e1/29/4bd7ae72a2634da00379c2b3bcc5439e7c94620235c6afea8af15229a973/backrefs-8.0-py311-none-any.whl", hash = "sha256:f0c35cf0102ba6b6070c12a492be3c1c1d3f5839529784b9a9565d6d04569a01", size = 392169, upload-time = "2026-07-26T19:54:18.782Z" }, + { url = "https://files.pythonhosted.org/packages/29/13/232505664e8e2a0c7a2eb0c505cfade9d715538f89a5d62bc4c272968f62/backrefs-8.0-py312-none-any.whl", hash = "sha256:87f0fae8c5f207fe9f4b2887efc71d42f4900ac78faa1af08d675ef303692dc5", size = 398084, upload-time = "2026-07-26T19:54:19.954Z" }, + { url = "https://files.pythonhosted.org/packages/8a/69/47a3dc20abc4fa5486655fde681bd55e63211b46c886d8c02223d6468431/backrefs-8.0-py313-none-any.whl", hash = "sha256:601ce68ca12385dbda06ce264406b4c4210cf5b79fd0fd627592365c92f29a88", size = 400040, upload-time = "2026-07-26T19:54:21.194Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cf/e5f9b68a5b0e939a2fb933a66c20180d0c9241bf8927f7a47fa48c1675e9/backrefs-8.0-py314-none-any.whl", hash = "sha256:9ec96efa080938be92323e8e730e57718c9c88eb15ad70bbef4e1766df591408", size = 411903, upload-time = "2026-07-26T19:54:23.221Z" }, ] [[package]] @@ -2783,27 +2783,27 @@ wheels = [ [[package]] name = "ty" -version = "0.0.63" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/ce/cbeaa5c7576fec643609dfbf200d59493523b1cc0481d4e7a5effcbf0630/ty-0.0.63.tar.gz", hash = "sha256:c2f66439393b3acac69306c117d4ae44638ce5fffa4a20c21046e85bd473359f", size = 6280695, upload-time = "2026-07-23T11:41:39.845Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/e4/d17a8e113ab15c692fe6fb9c422112d4bee7e829d80ced35826b45d98d98/ty-0.0.63-py3-none-linux_armv6l.whl", hash = "sha256:9a4ef7782e3af314fb63d006bec5ac3025bd70fee774b4dcfb5e44e8564f1994", size = 12056302, upload-time = "2026-07-23T11:41:03.5Z" }, - { url = "https://files.pythonhosted.org/packages/be/0b/357234c815dc4bfcc88c3f860aa0983fe4228c8aa2a5bb16c35ee08a94af/ty-0.0.63-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:01eab0ab70d51ad10298aa2d4b058b387a1fe93e5ee52d2a1ee23e9c69ba8354", size = 11737674, upload-time = "2026-07-23T11:41:05.862Z" }, - { url = "https://files.pythonhosted.org/packages/1c/13/193d9aeeb6774690351cff9fafabd3ae9b54cc225d125e07fa004ce23bdc/ty-0.0.63-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a671b61eaad16178389e05b9c108c9cb75ae8d84968fe55906484f55d6268338", size = 11264191, upload-time = "2026-07-23T11:41:07.963Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b7/c5843e16759a2b25fc8a7197473474038e585ac9fdaa6fa16aeb6384bf6a/ty-0.0.63-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b255cc83d95c51bb9ee4931303fdbece8cb1d6d7c655eb77a3f2c8f349fb64d6", size = 11818890, upload-time = "2026-07-23T11:41:09.885Z" }, - { url = "https://files.pythonhosted.org/packages/06/20/adf83ae1fc570d9bb449605e1eeeaa523ad2329ca838a636698b5c135d11/ty-0.0.63-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0da1e8183aa4f893421a173904478021498f542ee41273660538da6649a9631c", size = 11853141, upload-time = "2026-07-23T11:41:12.151Z" }, - { url = "https://files.pythonhosted.org/packages/ea/90/f8effd846e3ee13486ea08257c13094d58b9f188c5641bf609d6a7d5c09f/ty-0.0.63-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:128f38eb5a67199e3811426386f7ec96f41251ddf45e04ddc0bcbb29d4853245", size = 12545184, upload-time = "2026-07-23T11:41:14.4Z" }, - { url = "https://files.pythonhosted.org/packages/b8/76/aac3a30d40431eb1d329acea016b248f5b6255e30ef3d28e19447e344be2/ty-0.0.63-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bedf34aff8b0557f2a7119b314a68a9c9e58c1d21554d0e2748f2c5fe6a1f638", size = 13062375, upload-time = "2026-07-23T11:41:16.441Z" }, - { url = "https://files.pythonhosted.org/packages/62/3d/0158733932893e17f6008dadd74c8d7aed422fefc66e47fe77888014fe8c/ty-0.0.63-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7328d63c34587606dce02935a25404f54cd0161bfe413b8f7fc21d174aead612", size = 12619461, upload-time = "2026-07-23T11:41:18.538Z" }, - { url = "https://files.pythonhosted.org/packages/f5/be/e280ad095b050778f16f493d49a073fa5f6d8f301d3e2e59be6a672ba05c/ty-0.0.63-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504c4457f3a62afe836c1f26a2c9a12549299095f9cc4146778558df51a7515c", size = 12376402, upload-time = "2026-07-23T11:41:20.482Z" }, - { url = "https://files.pythonhosted.org/packages/57/57/619788bf335cb86b090470b557ae1eed33613dc24e12142505d32b462e58/ty-0.0.63-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7394ed39424b027c89d5f0c818871c791e33958b88f5bb13e14bd4a12a3ca631", size = 12671377, upload-time = "2026-07-23T11:41:22.489Z" }, - { url = "https://files.pythonhosted.org/packages/07/a2/0aff2cdd3c98e2c4729337542b8da0ce1980790e1600e0c4a717a1f46293/ty-0.0.63-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:94c3f155230490f8fb505911655e940c630c25cc0c35a76690cb413254fb4437", size = 11768090, upload-time = "2026-07-23T11:41:24.558Z" }, - { url = "https://files.pythonhosted.org/packages/43/4a/cdb5f1d26154144dfee08e1bd671daf827238170f7cee9dad43990bb2016/ty-0.0.63-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c16e1d8b4f0ae99106d5f13a894034a202baf6cdab61e2bb1a239de82904f839", size = 11867747, upload-time = "2026-07-23T11:41:26.794Z" }, - { url = "https://files.pythonhosted.org/packages/7e/26/ecc09ecb70bc9fbfdf42fa57ff29568f173e8200df575a0d72dc2b8486f9/ty-0.0.63-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b66293dad89eaed4b9fbf3b661614dbeb85b59ba037178a3f9a0adedf264da5c", size = 12120000, upload-time = "2026-07-23T11:41:28.731Z" }, - { url = "https://files.pythonhosted.org/packages/14/07/862822f9c2c397785b69b25cf79b4dfc3c0d55684b9adf11ac194450f8e1/ty-0.0.63-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8b29cc832e2ec73502c97dd7325d6ae0e085b34a33529a0f785192317d9862fc", size = 12477862, upload-time = "2026-07-23T11:41:30.847Z" }, - { url = "https://files.pythonhosted.org/packages/d0/c2/50b08b641578d6f48e8aa28a91d0de32c91aa24dd926ed199e8afd2d37ea/ty-0.0.63-py3-none-win32.whl", hash = "sha256:f0a8fbfd1f990c0c5d85cce018d438969ac79e49247e2192c9745394bb7d17ab", size = 11433155, upload-time = "2026-07-23T11:41:33.28Z" }, - { url = "https://files.pythonhosted.org/packages/92/2d/d422a5568f0d1f317186be22241288dc6e08b71dc24aca223f22038ac2d2/ty-0.0.63-py3-none-win_amd64.whl", hash = "sha256:2aa2370bdd6f42e9f37518c812379bef70b9162f9e5aad511495229b0b71cb93", size = 12450036, upload-time = "2026-07-23T11:41:35.559Z" }, - { url = "https://files.pythonhosted.org/packages/35/97/2c9748e28ead0650c7ad3e5f74f178832ceabd7cb5c272a882f29eb32ee4/ty-0.0.63-py3-none-win_arm64.whl", hash = "sha256:95ac1a62162c3c7ac204731e95ebf766d62a46a7bfa238a6acbe923fcb772cb1", size = 11826243, upload-time = "2026-07-23T11:41:37.749Z" }, +version = "0.0.64" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/aa/14c9965d3b173105692473897cc89c34cd91241368b2044e43167e1c17ff/ty-0.0.64.tar.gz", hash = "sha256:d12ddbb05f15158bb518af619378b385486450def95fb06f8ab98037febe9f2c", size = 6350966, upload-time = "2026-07-27T18:32:45.403Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/4c/c54937e4ff3fa7b34a99ea3387ec766bf0ad98dc8df8d792e89b388e658e/ty-0.0.64-py3-none-linux_armv6l.whl", hash = "sha256:3830a6675ab43635ced1c4c557f380ac4a49e9414e03975e4e4e8db644c64944", size = 12118357, upload-time = "2026-07-27T18:32:08.702Z" }, + { url = "https://files.pythonhosted.org/packages/ce/aa/a839ee2bc78e943d079e6abe199a97b4eeffb7e5c9a57326d69de452186a/ty-0.0.64-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3ff07d7bc32a2135f58afe57393789a32ea2fed1a66216129a8e535e76043903", size = 11790882, upload-time = "2026-07-27T18:32:10.997Z" }, + { url = "https://files.pythonhosted.org/packages/4c/de/19f14357888a7198438926303753cf749428e3d62e8980ff1e9a72a78402/ty-0.0.64-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4f6d1c7f897cca05d12bacbf1435150d5ffa496099515aa6ed303c8b29e1d0bb", size = 11317394, upload-time = "2026-07-27T18:32:13.162Z" }, + { url = "https://files.pythonhosted.org/packages/08/2f/f54462300535ab99b551eda733177be2eef5dbc2997d3fdb357c4ddd760a/ty-0.0.64-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:138d6c37ad4bf8583aa7a9b29d90954151d7856f9910a03eae3eb34b34c57215", size = 11863042, upload-time = "2026-07-27T18:32:15.307Z" }, + { url = "https://files.pythonhosted.org/packages/5e/95/dbecf745520ebe8bd7b02fc55eee6441c9be312ebf6addce605eb52740dd/ty-0.0.64-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ed9719d1b7b66fb8efe073d860208a44c40af1f6cd5c2364aa9b323a1e579b4", size = 11910730, upload-time = "2026-07-27T18:32:17.467Z" }, + { url = "https://files.pythonhosted.org/packages/3b/26/12cfd40028e51ceed7b3cb645281c61c02eb64ff9fb0c09231d65c30ff25/ty-0.0.64-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d68b23e5169e2137b5f1de7169ab0cebecab6c8eda374c34c1f6394308f58242", size = 12631936, upload-time = "2026-07-27T18:32:19.533Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d0/65ffc2b0a686347193c6f98e9421a7fc2a96fc3cd0b1001cf7cf284baff9/ty-0.0.64-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f41cb07d89d32626fcaf3ed4d262778fcb28b2628b6ed1e172cd7b18820668d8", size = 13171049, upload-time = "2026-07-27T18:32:22.026Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a4/975a5961842dcd6fa60f0770a102c0bba7509da909ab652919bfcdcd4fc7/ty-0.0.64-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5f24e1504ab9e212f92356b82fe088fdeb3a39f9a2f4ff25e505d2e1d0db9056", size = 12826438, upload-time = "2026-07-27T18:32:24.178Z" }, + { url = "https://files.pythonhosted.org/packages/af/ef/dfb9b7f9bcc032d3b540b0d1f55f532a336e2fb41b1bd539c05ae81a151a/ty-0.0.64-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86db830cb914bb33bb8247b66ccea58de4496c2391bd42658aba744b439f3290", size = 12440880, upload-time = "2026-07-27T18:32:26.341Z" }, + { url = "https://files.pythonhosted.org/packages/ee/d0/bedac20505e8a8f5501ad73d7d15d8e421a563fef59993909a23036929a4/ty-0.0.64-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:652ee3d6d03bea76cd2fe8949c78bb5970394ebd7c5fd270e9518c0dee1b931d", size = 12782439, upload-time = "2026-07-27T18:32:28.642Z" }, + { url = "https://files.pythonhosted.org/packages/79/d5/795733f13ceff1378f08b3de0c49d0f518df220ed856b0dfac869f3b7c81/ty-0.0.64-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4838768295774a86e95f9ec5633e739d016adbbb69dcbdc62f3549578a11f624", size = 11814821, upload-time = "2026-07-27T18:32:30.632Z" }, + { url = "https://files.pythonhosted.org/packages/52/3e/9d99cd1e1831003434f508ed9f258a56543194afc3bbe051eba2545fa676/ty-0.0.64-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5a3d700669868599edf39ce5125196682f15a8880cc8c669bc2a83b99984d99b", size = 11928678, upload-time = "2026-07-27T18:32:32.888Z" }, + { url = "https://files.pythonhosted.org/packages/b8/3d/448f49a3503fb119a34348a5714bb92001f252fadbbb12d645f2e744b557/ty-0.0.64-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b161f0a82a8e2f2432db3bf7702b4d3924fa9486ba0014f6710a160fc157df0d", size = 12202249, upload-time = "2026-07-27T18:32:34.905Z" }, + { url = "https://files.pythonhosted.org/packages/dd/9b/75768e562cec990d189dc05807ae72890b20ffbd1e1f43597bb98db63c60/ty-0.0.64-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:39b9dd42908df47c2dc57dda87e656fab97097ffd2618474bbdea986af0d6a9d", size = 12548817, upload-time = "2026-07-27T18:32:36.995Z" }, + { url = "https://files.pythonhosted.org/packages/34/89/44cc276ea6ca0245495014758ffeadde1798fb0b5840c9cf36d8d2ed3250/ty-0.0.64-py3-none-win32.whl", hash = "sha256:d0676ab0e0935795e5843baa28dd5e366dc343d7c0945a996df9eab8e0644885", size = 11545474, upload-time = "2026-07-27T18:32:39.389Z" }, + { url = "https://files.pythonhosted.org/packages/01/7e/d1c8a871a38d17c8f168b9a6975f6247f7660f8334e517656a5e4b4a4858/ty-0.0.64-py3-none-win_amd64.whl", hash = "sha256:dcb9bd31f54097e362b776c26ab4564d4564cdd1355cb883481167a26c03cc3f", size = 12542987, upload-time = "2026-07-27T18:32:41.412Z" }, + { url = "https://files.pythonhosted.org/packages/35/4d/6d18640d0204cacd69abbaca95ad6a34c6d7e9169e9051d0117b17b827ec/ty-0.0.64-py3-none-win_arm64.whl", hash = "sha256:82cc34c1ad9a8feb6059aef193bebcecc656e548f6fab3d518bcd8b57d198d39", size = 11899263, upload-time = "2026-07-27T18:32:43.366Z" }, ] [[package]] From e65fc65cf3188949514273d58c21f5170db6d1a3 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Tue, 28 Jul 2026 00:10:20 -0700 Subject: [PATCH 4/5] update deps --- bun.lock | 9 --------- package.json | 8 +------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/bun.lock b/bun.lock index 845716ab..6c87b2e5 100644 --- a/bun.lock +++ b/bun.lock @@ -38,7 +38,6 @@ "prettier-plugin-nginx": "^1.0.3", "prettier-plugin-packagejson": "^3.0.2", "prettier-plugin-toml": "^2.0.6", - "prettier-plugin-xml": "^0.1.0", "remark-cli": "^12.0.1", "remark-gfm": "^4.0.1", "remark-lint-no-duplicate-headings-in-section": "^4.0.1", @@ -437,8 +436,6 @@ "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - "fast-xml-parser": ["fast-xml-parser@3.21.1", "", { "dependencies": { "strnum": "^1.0.4" }, "bin": { "xml2js": "cli.js" } }, "sha512-FTFVjYoBOZTJekiUsawGsSYV9QL0A+zDYCRj7y34IO6Jg+2IMYEtQa+bbictpdpV8dHxXywqU7C0gRDEOFtBFg=="], - "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], @@ -767,8 +764,6 @@ "prettier-plugin-toml": ["prettier-plugin-toml@2.0.6", "", { "dependencies": { "@taplo/lib": "^0.5.0" }, "peerDependencies": { "prettier": "^3.0.3" } }, "sha512-12N/wBuHa9jd/KVy9pRP20NMKxQfQLMseQCt66lIbLaPLItvGUcSIryE1eZZMJ7loSws6Ig3M2Elc2EreNh76w=="], - "prettier-plugin-xml": ["prettier-plugin-xml@0.1.0", "", { "dependencies": { "fast-xml-parser": "^3.14.0", "prettier": ">=1.10" } }, "sha512-rywliZ83SVxLT5+OOWjT28g8jgshks61+nN+9e7ss6N519sz5UPS0/NzaS9M+qUYY9GVN0JXEa4R8aJPlMrRAA=="], - "proc-log": ["proc-log@6.1.0", "", {}, "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ=="], "promise-inflight": ["promise-inflight@1.0.1", "", {}, "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="], @@ -977,8 +972,6 @@ "strip-indent": ["strip-indent@4.1.1", "", {}, "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA=="], - "strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="], - "super-regex": ["super-regex@1.1.0", "", { "dependencies": { "function-timeout": "^1.0.1", "make-asynchronous": "^1.0.1", "time-span": "^5.1.0" } }, "sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ=="], "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], @@ -1185,8 +1178,6 @@ "prettier-plugin-packagejson/sort-package-json": ["sort-package-json@3.6.1", "", { "dependencies": { "detect-indent": "^7.0.2", "detect-newline": "^4.0.1", "git-hooks-list": "^4.1.1", "is-plain-obj": "^4.1.0", "semver": "^7.7.3", "sort-object-keys": "^2.0.1", "tinyglobby": "^0.2.15" }, "bin": "cli.js" }, "sha512-Chgejw1+10p2D0U2tB7au1lHtz6TkFnxmvZktyBCRyV0GgmF6nl1IxXxAsPtJVsUyg/fo+BfCMAVVFUVRkAHrQ=="], - "prettier-plugin-xml/prettier": ["prettier@3.8.2", "", { "bin": "bin/prettier.cjs" }, "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q=="], - "readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "sort-package-json/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], diff --git a/package.json b/package.json index 4ddff532..b79dd719 100644 --- a/package.json +++ b/package.json @@ -54,12 +54,7 @@ "preset-lint-consistent", "preset-lint-markdown-style-guide", "preset-lint-recommended", - "preset-prettier", - [ - "lint-no-duplicate-headings", - false - ], - "lint-no-duplicate-headings-in-section" + "preset-prettier" ] }, "devDependencies": { @@ -97,7 +92,6 @@ "prettier-plugin-nginx": "^1.0.3", "prettier-plugin-packagejson": "^3.0.2", "prettier-plugin-toml": "^2.0.6", - "prettier-plugin-xml": "^0.1.0", "remark-cli": "^12.0.1", "remark-gfm": "^4.0.1", "remark-lint-no-duplicate-headings-in-section": "^4.0.1", From 9c5ae01248a8ce65a0573562197ef631a1d4a7aa Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Tue, 28 Jul 2026 00:10:49 -0700 Subject: [PATCH 5/5] update deps --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9a2bbb01..820a1176 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,8 +20,8 @@ dependencies = [ "oxipng-pybind>=1.0", "piexif~=1.1", "pikepdf>=10.5.1", - "Pillow>=12.0", "pillow-jxl-plugin~=1.3", + "Pillow>=12.0", "py7zr~=1.0", "python-dateutil~=2.8", "rarfile~=4.0",