Skip to content

Commit

Permalink
modified libheif_info function. (#189)
Browse files Browse the repository at this point in the history
Signed-off-by: Alexander Piskun <[email protected]>
  • Loading branch information
bigcat88 authored Dec 26, 2023
1 parent f17d541 commit 6de326f
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 33 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
All notable changes to this project will be documented in this file.

## [0.1x.x - 2024-0x-xx]
## [0.15.0 - 2024-01-xx]

### Added

- `libheif_info` function: added `encoders` and `decoders` keys to the result, for future libheif plugins support. #189

### Changed

Expand Down
23 changes: 17 additions & 6 deletions pillow_heif/_lib_info.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Functions to get versions of underlying libraries."""

import typing

try:
import _pillow_heif
except ImportError as ex:
Expand All @@ -10,18 +12,27 @@

def libheif_version() -> str:
"""Returns ``libheif`` version."""
return _pillow_heif.lib_info["libheif"]
return _pillow_heif.get_lib_info()["libheif"]


def libheif_info() -> dict:
def libheif_info() -> dict[str, typing.Union[str, dict[str, str]]]:
"""Returns a dictionary with version information.
The keys `libheif`, `HEIF`, `AVIF` are always present, but values for `HEIF`/`AVIF` can be empty.
The keys `libheif`, `HEIF`, `AVIF`, `encoders`, `decoders` are always present, but values for all except
`libheif` can be empty.
{
'libheif': '1.14.2',
'libheif': '1.15.2',
'HEIF': 'x265 HEVC encoder (3.4+31-6722fce1f)',
'AVIF': 'AOMedia Project AV1 Encoder 3.5.0'
'AVIF': 'AOMedia Project AV1 Encoder 3.5.0',
'encoders': {
'encoder1_id': 'encoder1_full_name',
'encoder2_id': 'encoder2_full_name',
},
'decoders': {
'decoder1_id': 'decoder1_full_name',
'decoder2_id': 'decoder2_full_name',
},
}
"""
return _pillow_heif.lib_info
return _pillow_heif.get_lib_info()
60 changes: 42 additions & 18 deletions pillow_heif/_pillow_heif.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

/* =========== Common stuff ======== */

#define MAX_ENCODERS 20
#define MAX_DECODERS 20

#define RETURN_NONE Py_INCREF(Py_None); return Py_None;

static struct heif_error heif_error_no = { .code = 0, .subcode = 0, .message = "" };
Expand Down Expand Up @@ -1268,11 +1271,50 @@ static PyObject* _load_file(PyObject* self, PyObject* args) {
return images_list;
}

static PyObject* _get_lib_info(PyObject* self) {
PyObject* lib_info_dict = PyDict_New();
PyObject* encoders_dict = PyDict_New();
PyObject* decoders_dict = PyDict_New();
if ((!lib_info_dict) || (!encoders_dict) || (!decoders_dict)) {
PyErr_SetString(PyExc_OSError, "Out of Memory");
return NULL;
}
__PyDict_SetItemString(lib_info_dict, "libheif", PyUnicode_FromString(heif_get_version()));

const struct heif_encoder_descriptor* encoder_descriptor;
const char* x265_version = "";
if (heif_get_encoder_descriptors(heif_compression_HEVC, NULL, &encoder_descriptor, 1))
x265_version = heif_encoder_descriptor_get_name(encoder_descriptor);
__PyDict_SetItemString(lib_info_dict, "HEIF", PyUnicode_FromString(x265_version));
const char* aom_version = "";
if (heif_get_encoder_descriptors(heif_compression_AV1, NULL, &encoder_descriptor, 1))
aom_version = heif_encoder_descriptor_get_name(encoder_descriptor);
__PyDict_SetItemString(lib_info_dict, "AVIF", PyUnicode_FromString(aom_version));

__PyDict_SetItemString(lib_info_dict, "encoders", encoders_dict);
__PyDict_SetItemString(lib_info_dict, "decoders", decoders_dict);

const struct heif_encoder_descriptor* encoders[MAX_ENCODERS];
int encoders_count = heif_get_encoder_descriptors(heif_compression_undefined, NULL, encoders, MAX_ENCODERS);
for (int i = 0; i < encoders_count; i++) {
__PyDict_SetItemString(encoders_dict, heif_encoder_descriptor_get_id_name(encoders[i]), PyUnicode_FromString(heif_encoder_descriptor_get_name(encoders[i])));
}

const struct heif_decoder_descriptor* decoders[MAX_DECODERS];
int decoders_count = heif_get_decoder_descriptors(heif_compression_undefined, decoders, MAX_DECODERS);
for (int i = 0; i < decoders_count; i++) {
__PyDict_SetItemString(decoders_dict, heif_decoder_descriptor_get_id_name(decoders[i]), PyUnicode_FromString(heif_decoder_descriptor_get_name(decoders[i])));
}

return lib_info_dict;
}

/* =========== Module =========== */

static PyMethodDef heifMethods[] = {
{"CtxWrite", (PyCFunction)_CtxWrite, METH_VARARGS},
{"load_file", (PyCFunction)_load_file, METH_VARARGS},
{"get_lib_info", (PyCFunction)_get_lib_info, METH_NOARGS},
{NULL, NULL}
};

Expand Down Expand Up @@ -1307,8 +1349,6 @@ static PyTypeObject CtxImage_Type = {
};

static int setup_module(PyObject* m) {
PyObject* d = PyModule_GetDict(m);

if (PyType_Ready(&CtxWriteImage_Type) < 0)
return -1;

Expand All @@ -1319,22 +1359,6 @@ static int setup_module(PyObject* m) {
return -1;

heif_init(NULL);

const struct heif_encoder_descriptor* encoder_descriptor;
const char* x265_version = "";
if (heif_context_get_encoder_descriptors(NULL, heif_compression_HEVC, NULL, &encoder_descriptor, 1))
x265_version = heif_encoder_descriptor_get_name(encoder_descriptor);
const char* aom_version = "";
if (heif_context_get_encoder_descriptors(NULL, heif_compression_AV1, NULL, &encoder_descriptor, 1))
aom_version = heif_encoder_descriptor_get_name(encoder_descriptor);

PyObject* version_dict = PyDict_New();
__PyDict_SetItemString(version_dict, "libheif", PyUnicode_FromString(heif_get_version()));
__PyDict_SetItemString(version_dict, "HEIF", PyUnicode_FromString(x265_version));
__PyDict_SetItemString(version_dict, "AVIF", PyUnicode_FromString(aom_version));

if (__PyDict_SetItemString(d, "lib_info", version_dict) < 0)
return -1;
return 0;
}

Expand Down
2 changes: 1 addition & 1 deletion pillow_heif/_version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Version of pillow_heif/pi_heif."""

__version__ = "0.14.0"
__version__ = "0.15.0.dev0"
4 changes: 2 additions & 2 deletions pillow_heif/as_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def register_heif_opener(**kwargs) -> None:
"""
__options_update(**kwargs)
Image.register_open(HeifImageFile.format, HeifImageFile, _is_supported_heif)
if _pillow_heif.lib_info["HEIF"]:
if _pillow_heif.get_lib_info()["HEIF"]:
Image.register_save(HeifImageFile.format, _save_heif)
Image.register_save_all(HeifImageFile.format, _save_all_heif)
extensions = [".heic", ".heics", ".heif", ".heifs", ".hif"]
Expand Down Expand Up @@ -216,7 +216,7 @@ def register_avif_opener(**kwargs) -> None:
:param kwargs: dictionary with values to set in options. See: :ref:`options`.
"""
if not _pillow_heif.lib_info["AVIF"]:
if not _pillow_heif.get_lib_info()["AVIF"]:
warn("This version of `pillow-heif` was built without AVIF support.", stacklevel=1)
return
__options_update(**kwargs)
Expand Down
2 changes: 1 addition & 1 deletion pillow_heif/heif.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ def encode(mode: str, size: tuple, data, fp, **kwargs) -> None:
def _encode_images(images: List[HeifImage], fp, **kwargs) -> None:
compression = kwargs.get("format", "HEIF")
compression_format = HeifCompressionFormat.AV1 if compression == "AVIF" else HeifCompressionFormat.HEVC
if not _pillow_heif.lib_info[compression]:
if not _pillow_heif.get_lib_info()[compression]:
raise RuntimeError(f"No {compression} encoder found.")
images_to_save: List[HeifImage] = images + kwargs.get("append_images", [])
if not kwargs.get("save_all", True):
Expand Down
7 changes: 4 additions & 3 deletions tests/basic_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@

def test_libheif_info():
info = pillow_heif.libheif_info()
for key in ("HEIF", "AVIF"):
for key in ("HEIF", "AVIF", "encoders", "decoders"):
assert key in info
assert pillow_heif.libheif_version() in (
"1.14.1",
"1.14.2",
"1.15.1",
"1.15.2",
"1.16.1",
Expand Down Expand Up @@ -112,6 +110,8 @@ def test_full_build():
info = pillow_heif.libheif_info()
assert info["AVIF"]
assert info["HEIF"]
assert info["encoders"]
assert info["decoders"]
expected_version = os.getenv("EXP_PH_LIBHEIF_VERSION", "1.17.6")
if expected_version:
assert info["libheif"] == expected_version
Expand All @@ -122,6 +122,7 @@ def test_light_build():
info = pillow_heif.libheif_info()
assert not info["AVIF"]
assert not info["HEIF"]
assert info["decoders"]
expected_version = os.getenv("EXP_PH_LIBHEIF_VERSION", "1.17.6")
if expected_version:
assert info["libheif"] == expected_version
5 changes: 4 additions & 1 deletion tests/write_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,10 @@ def test_pillow_save_one_all():


def test_heif_no_encoder():
with mock.patch.dict("pillow_heif.heif._pillow_heif.lib_info", {"libheif": "1.14.2", "HEIF": "", "AVIF": ""}):
def get_lib_info():
return {"libheif": "1.17.5", "HEIF": "", "AVIF": "", "encoders": {}, "decoders": {}}

with mock.patch("pillow_heif.heif._pillow_heif.get_lib_info", side_effect=get_lib_info):
im_heif = pillow_heif.from_pillow(Image.new("L", (64, 64)))
out_buffer = BytesIO()
with pytest.raises(RuntimeError):
Expand Down

0 comments on commit 6de326f

Please sign in to comment.