Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 4 additions & 13 deletions Tests/test_file_webp.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import io
import re
import sys
import warnings
from typing import Any

Expand Down Expand Up @@ -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:
"""
Expand Down
6 changes: 6 additions & 0 deletions docs/releasenotes/13.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,9 @@ position or contents.

:py:meth:`~PIL.Image.Image.tobytes` also now explicitly cleans up its encoder on
both success and failure, rather than relying on object destruction.

WebP encoding errors now include a description
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

When libwebp fails to encode an image, the raised :py:exc:`ValueError` now
consistently describes the error rather than only reporting a numeric code.
39 changes: 29 additions & 10 deletions src/_webp.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,30 @@ HandleMuxError(WebPMuxError err, char *chunk) {
return NULL;
}

/* -------------------------------------------------------------------- */
/* WebP Encoder Error Mapping */
/* -------------------------------------------------------------------- */

#define WEBP_STR_HELPER(x) #x
#define WEBP_STR(x) WEBP_STR_HELPER(x)

static const char *const kEncoderErrorMessages[VP8_ENC_ERROR_LAST] = {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

"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",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What is the story with WEBP_STR and WEBP_STR_HELPER? Why do they need to be defined separately, and why are there two of them?

@akx akx Sep 22, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That's just C preprocessor expansion rules and standard procedure:

a.c

#define WEBP_MAX_DIMENSION 123

// Doesn't work like we want
#define WEBP_STR_1(x) #x
WEBP_STR_1(WEBP_MAX_DIMENSION)

// Works like we want
#define WEBP_STR_HELPER(x) #x
#define WEBP_STR_2(x) WEBP_STR_HELPER(x)
WEBP_STR_2(WEBP_MAX_DIMENSION)

Preprocessing:

$ gcc -E a.c
# 1 "a.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 466 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "a.c" 2



"WEBP_MAX_DIMENSION"




"123"

This shows up in Pillow e.g. here (#4700):

Pillow/src/_imaging.c

Lines 4372 to 4374 in 4b03987

#define tostr1(a) #a
#define tostr(a) tostr1(a)
PyObject *v = PyUnicode_FromString(tostr(LIBJPEG_TURBO_VERSION));

"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"
};

#undef WEBP_STR
#undef WEBP_STR_HELPER

/* -------------------------------------------------------------------- */
/* Frame import */
/* -------------------------------------------------------------------- */
Expand Down Expand Up @@ -647,17 +671,12 @@ 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);
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;
}

Expand Down
Loading