From 795694a544a7a5d73aa9b22bd2a8c1d9367f8394 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 22 Sep 2026 13:33:43 +0300 Subject: [PATCH 1/3] Improve WebP encoder error handling Follows up on #5471 and #9993. Instead of reporting just "error 6", this PR adds human-readable error messages for the WebP encoder errors. The error messages are based on the WebP header. The memory-hungry `test_write_encoding_error_message` test case (that tested "partition is bigger than 512k") is no longer necessary, since `test_write_encoding_error_bad_dimension` covers the same code path. --- Tests/test_file_webp.py | 17 ++++----------- docs/releasenotes/13.0.0.rst | 6 ++++++ src/_webp.c | 42 ++++++++++++++++++++++++++---------- 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 14a2d2317a1..b0a8c51ba01 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -2,7 +2,6 @@ import io import re -import sys import warnings from typing import Any @@ -158,21 +157,13 @@ def test_write_unsupported_mode_P(self, tmp_path: Path) -> None: self._roundtrip(tmp_path, "P", 50.0) - @pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system") - def test_write_encoding_error_message(self, tmp_path: Path) -> None: - im = Image.new("RGB", (15000, 15000)) - with pytest.raises(ValueError, match="encoding error 6"): - im.save(tmp_path / "temp.webp", method=0) - - @pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system") def test_write_encoding_error_bad_dimension(self, tmp_path: Path) -> None: im = Image.new("L", (16384, 1)) - with pytest.raises(ValueError) as e: + with pytest.raises( + ValueError, + match="encoding error 5: image size exceeds WebP limit of 16383 pixels", + ): im.save(tmp_path / "temp.webp") - assert ( - str(e.value) - == "encoding error 5: Image size exceeds WebP limit of 16383 pixels" - ) def test_WebPEncode_with_invalid_args(self) -> None: """ diff --git a/docs/releasenotes/13.0.0.rst b/docs/releasenotes/13.0.0.rst index 694093b01ab..403a785438c 100644 --- a/docs/releasenotes/13.0.0.rst +++ b/docs/releasenotes/13.0.0.rst @@ -134,6 +134,12 @@ escape sequences. Other changes ============= +WebP encoding errors now include a description +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When libwebp fails to encode an image, the raised :py:exc:`ValueError` now +describes the error rather than only reporting a numeric code. + PNG palettes are no longer padded when saving with the ``bits`` argument ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/_webp.c b/src/_webp.c index 9f9fbf3978a..b78cd155d9c 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -73,6 +73,36 @@ HandleMuxError(WebPMuxError err, char *chunk) { return NULL; } +/* -------------------------------------------------------------------- */ +/* WebP Encoder Error Handling */ +/* -------------------------------------------------------------------- */ + +#define WEBP_STR_HELPER(x) #x +#define WEBP_STR(x) WEBP_STR_HELPER(x) + +static const char *const kEncoderErrorMessages[VP8_ENC_ERROR_LAST] = { + "ok", + "out of memory allocating objects", + "out of memory re-allocating byte buffer", + "NULL parameter passed to function", + "configuration is invalid", + "image size exceeds WebP limit of " WEBP_STR(WEBP_MAX_DIMENSION) " pixels", + "partition #0 is bigger than 512K", + "partition is bigger than 16M", + "picture writer returned an I/O error", + "file would be bigger than 4G", + "encoding aborted by user" +}; + +static PyObject * +HandleEncoderError(WebPEncodingError error_code) { + const char *message = error_code > VP8_ENC_OK && error_code < VP8_ENC_ERROR_LAST + ? kEncoderErrorMessages[error_code] + : "unknown error"; + PyErr_Format(PyExc_ValueError, "encoding error %d: %s", error_code, message); + return NULL; +} + /* -------------------------------------------------------------------- */ /* Frame import */ /* -------------------------------------------------------------------- */ @@ -647,18 +677,8 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) { ret_size = writer.size; if (!ok) { - int error_code = (&pic)->error_code; - char message[50] = ""; - if (error_code == VP8_ENC_ERROR_BAD_DIMENSION) { - sprintf( - message, - ": Image size exceeds WebP limit of %d pixels", - WEBP_MAX_DIMENSION - ); - } - PyErr_Format(PyExc_ValueError, "encoding error %d%s", error_code, message); free(output); - return NULL; + return HandleEncoderError(pic.error_code); } { From 1a3db31389fddef0b524a36f806ad629a20f3b60 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 23 Sep 2026 08:51:42 +0300 Subject: [PATCH 2/3] Inline HandleEncoderError --- src/_webp.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/_webp.c b/src/_webp.c index b78cd155d9c..5aa7cb170ec 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -74,7 +74,7 @@ HandleMuxError(WebPMuxError err, char *chunk) { } /* -------------------------------------------------------------------- */ -/* WebP Encoder Error Handling */ +/* WebP Encoder Error Mapping */ /* -------------------------------------------------------------------- */ #define WEBP_STR_HELPER(x) #x @@ -94,14 +94,8 @@ static const char *const kEncoderErrorMessages[VP8_ENC_ERROR_LAST] = { "encoding aborted by user" }; -static PyObject * -HandleEncoderError(WebPEncodingError error_code) { - const char *message = error_code > VP8_ENC_OK && error_code < VP8_ENC_ERROR_LAST - ? kEncoderErrorMessages[error_code] - : "unknown error"; - PyErr_Format(PyExc_ValueError, "encoding error %d: %s", error_code, message); - return NULL; -} +#undef WEBP_STR +#undef WEBP_STR_HELPER /* -------------------------------------------------------------------- */ /* Frame import */ @@ -678,7 +672,12 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) { if (!ok) { free(output); - return HandleEncoderError(pic.error_code); + WebPEncodingError error_code = pic.error_code; + const char *message = error_code > VP8_ENC_OK && error_code < VP8_ENC_ERROR_LAST + ? kEncoderErrorMessages[error_code] + : "unknown error"; + PyErr_Format(PyExc_ValueError, "encoding error %d: %s", error_code, message); + return NULL; } { From e4142eec5440effbf9c5230bb87224175066113d Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 23 Sep 2026 08:52:37 +0300 Subject: [PATCH 3/3] Add "consistently" to changelog note --- docs/releasenotes/13.0.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releasenotes/13.0.0.rst b/docs/releasenotes/13.0.0.rst index 403a785438c..636d51a1d96 100644 --- a/docs/releasenotes/13.0.0.rst +++ b/docs/releasenotes/13.0.0.rst @@ -138,7 +138,7 @@ WebP encoding errors now include a description ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When libwebp fails to encode an image, the raised :py:exc:`ValueError` now -describes the error rather than only reporting a numeric code. +consistently describes the error rather than only reporting a numeric code. PNG palettes are no longer padded when saving with the ``bits`` argument ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^