Skip to content
Merged
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
5 changes: 5 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see
<http://www.gnu.org/licenses/>.

Changes in v0.19.0:
1/ Add exiv2.XmpParser.encode() to Python interface.
2/ Fix crash when getting strings of malformed values.
3/ Python 3.15 compatibility.

Changes in v0.18.1:
1/ Binary wheels incorporate libexiv2 v0.28.8

Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
python-exiv2 v\ 0.18.1
python-exiv2 v\ 0.19.0
======================

python-exiv2 is a low level interface (or binding) to the exiv2_ C++ library.
Expand Down
21 changes: 20 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
# python-exiv2 - Python interface to libexiv2
# http://github.com/jim-easterbrook/python-exiv2
# Copyright (C) 2023-26 Jim Easterbrook jim@jim-easterbrook.me.uk
#
# This file is part of python-exiv2.
#
# python-exiv2 is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# python-exiv2 is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with python-exiv2. If not, see <http://www.gnu.org/licenses/>.

[build-system]
requires = [
"setuptools >= 59.6, < 61.0; python_version < '3.9'",
"setuptools >= 77.0; python_version >= '3.9'",
"toml"
"toml; python_version < '3.9'"
]
build-backend = "setuptools.build_meta"

Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,13 @@ def get_mod_src_dir(exiv2_version):
library_dirs = [path]
package_dir['exiv2.lib'] = path
package_data['exiv2.lib'] = [x for x in os.listdir(path)
if re.fullmatch('libexiv2\.so\.\d+', x)]
if re.fullmatch(r'libexiv2\.so\.\d+', x)]
elif platform == 'darwin':
path = os.path.join(exiv2_root, 'lib')
library_dirs = [path]
package_dir['exiv2.lib'] = path
package_data['exiv2.lib'] = [x for x in os.listdir(path)
if re.fullmatch('libexiv2\.\d+\.dylib', x)]
if re.fullmatch(r'libexiv2\.\d+\.dylib', x)]
elif platform in ('win32', 'mingw'):
library_dirs = [os.path.join(exiv2_root, 'lib')]
package_dir['exiv2.lib'] = os.path.join(exiv2_root, 'bin')
Expand Down
2 changes: 1 addition & 1 deletion src/doc/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
exiv2 == 0.18.1
exiv2 == 0.19.0
sphinx == 8.2.3
sphinx-rtd-theme == 3.0.2
enum_tools[sphinx] >= 0.13
14 changes: 14 additions & 0 deletions src/interface/0_27_7/enum_members.i
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ static PyObject* _get_enum_data_%mangle(Exiv2::LogMsg::Level)() {
NULL);
};
}
%fragment("_get_enum_data"{Exiv2::XmpParser::XmpFormatFlags}, "header",
fragment="_get_enum_data") {
static PyObject* _get_enum_data_%mangle(Exiv2::XmpParser::XmpFormatFlags)() {
return _get_enum_data("Exiv2::XmpParser::XmpFormatFlags",
"omitPacketWrapper", Exiv2::XmpParser::omitPacketWrapper,
"readOnlyPacket", Exiv2::XmpParser::readOnlyPacket,
"useCompactFormat", Exiv2::XmpParser::useCompactFormat,
"includeThumbnailPad", Exiv2::XmpParser::includeThumbnailPad,
"exactPacketLength", Exiv2::XmpParser::exactPacketLength,
"writeAliasComments", Exiv2::XmpParser::writeAliasComments,
"omitAllFormatting", Exiv2::XmpParser::omitAllFormatting,
NULL);
};
}
%fragment("_get_enum_data"{Exiv2::XmpValue::XmpArrayType}, "header",
fragment="_get_enum_data") {
static PyObject* _get_enum_data_%mangle(Exiv2::XmpValue::XmpArrayType)() {
Expand Down
14 changes: 14 additions & 0 deletions src/interface/0_28_7/enum_members.i
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ static PyObject* _get_enum_data_%mangle(Exiv2::LogMsg::Level)() {
NULL);
};
}
%fragment("_get_enum_data"{Exiv2::XmpParser::XmpFormatFlags}, "header",
fragment="_get_enum_data") {
static PyObject* _get_enum_data_%mangle(Exiv2::XmpParser::XmpFormatFlags)() {
return _get_enum_data("Exiv2::XmpParser::XmpFormatFlags",
"omitPacketWrapper", Exiv2::XmpParser::omitPacketWrapper,
"readOnlyPacket", Exiv2::XmpParser::readOnlyPacket,
"useCompactFormat", Exiv2::XmpParser::useCompactFormat,
"includeThumbnailPad", Exiv2::XmpParser::includeThumbnailPad,
"exactPacketLength", Exiv2::XmpParser::exactPacketLength,
"writeAliasComments", Exiv2::XmpParser::writeAliasComments,
"omitAllFormatting", Exiv2::XmpParser::omitAllFormatting,
NULL);
};
}
%fragment("_get_enum_data"{Exiv2::XmpValue::XmpArrayType}, "header",
fragment="_get_enum_data") {
static PyObject* _get_enum_data_%mangle(Exiv2::XmpValue::XmpArrayType)() {
Expand Down
14 changes: 14 additions & 0 deletions src/interface/0_28_8/enum_members.i
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ static PyObject* _get_enum_data_%mangle(Exiv2::LogMsg::Level)() {
NULL);
};
}
%fragment("_get_enum_data"{Exiv2::XmpParser::XmpFormatFlags}, "header",
fragment="_get_enum_data") {
static PyObject* _get_enum_data_%mangle(Exiv2::XmpParser::XmpFormatFlags)() {
return _get_enum_data("Exiv2::XmpParser::XmpFormatFlags",
"omitPacketWrapper", Exiv2::XmpParser::omitPacketWrapper,
"readOnlyPacket", Exiv2::XmpParser::readOnlyPacket,
"useCompactFormat", Exiv2::XmpParser::useCompactFormat,
"includeThumbnailPad", Exiv2::XmpParser::includeThumbnailPad,
"exactPacketLength", Exiv2::XmpParser::exactPacketLength,
"writeAliasComments", Exiv2::XmpParser::writeAliasComments,
"omitAllFormatting", Exiv2::XmpParser::omitAllFormatting,
NULL);
};
}
%fragment("_get_enum_data"{Exiv2::XmpValue::XmpArrayType}, "header",
fragment="_get_enum_data") {
static PyObject* _get_enum_data_%mangle(Exiv2::XmpValue::XmpArrayType)() {
Expand Down
6 changes: 4 additions & 2 deletions src/interface/basicio.i
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// python-exiv2 - Python interface to libexiv2
// http://github.com/jim-easterbrook/python-exiv2
// Copyright (C) 2022-25 Jim Easterbrook jim@jim-easterbrook.me.uk
// Copyright (C) 2022-26 Jim Easterbrook jim@jim-easterbrook.me.uk
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -166,7 +166,9 @@ RETURN_VIEW(Exiv2::byte* mmap, $1 ? arg1->size() : 0,
%}
%typemap(check, fragment="memoryview_funcs") Exiv2::BasicIo* self {
%#ifdef RELEASE_VIEWS_$symname
release_views(self);
if (release_views(self) < 0) {
SWIG_fail;
}
%#endif
}

Expand Down
10 changes: 4 additions & 6 deletions src/interface/image.i
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// python-exiv2 - Python interface to libexiv2
// http://github.com/jim-easterbrook/python-exiv2
// Copyright (C) 2021-25 Jim Easterbrook jim@jim-easterbrook.me.uk
// Copyright (C) 2021-26 Jim Easterbrook jim@jim-easterbrook.me.uk
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -108,7 +108,9 @@ DEFINE_VIEW_CALLBACK(Exiv2::Image, release_ptr(&self->io());)
%}
%typemap(check, fragment="memoryview_funcs") Exiv2::Image* self {
%#ifdef RELEASE_VIEWS_$symname
release_views(self);
if (release_views(self) < 0) {
SWIG_fail;
}
%#endif
}

Expand Down Expand Up @@ -219,10 +221,6 @@ KEEP_REFERENCE(Exiv2::XmpData&)
KEEP_REFERENCE(Exiv2::DataBuf*)
KEEP_REFERENCE(Exiv2::DataBuf&)

// xmpPacket() returns a modifiable std::string, Python strings are immutable
// so treat it as a non-modifiable std::string
%apply const std::string& {std::string& xmpPacket};

#if EXIV2_VERSION_HEX < 0x001c0000
// Extend ImageType namespace with ones that don't get picked up by swig
%{
Expand Down
5 changes: 3 additions & 2 deletions src/interface/shared/containers.i
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// python-exiv2 - Python interface to libexiv2
// http://github.com/jim-easterbrook/python-exiv2
// Copyright (C) 2023-25 Jim Easterbrook jim@jim-easterbrook.me.uk
// Copyright (C) 2023-26 Jim Easterbrook jim@jim-easterbrook.me.uk
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -102,7 +102,8 @@ MP_ASS_SUBSCRIPT(Exiv2::base_class, PyObject*,
if (pos == self->end())
return PyErr_Format(PyExc_KeyError, "'%s'", key);
#if SWIG_VERSION >= 0x040400
invalidate_pointers(py_self, pos);
if (invalidate_pointers(py_self, pos) < 0)
return NULL;
#endif
self->erase(pos), false)
SQ_CONTAINS(
Expand Down
3 changes: 2 additions & 1 deletion src/interface/shared/keep_reference.i
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// python-exiv2 - Python interface to libexiv2
// http://github.com/jim-easterbrook/python-exiv2
// Copyright (C) 2023-25 Jim Easterbrook jim@jim-easterbrook.me.uk
// Copyright (C) 2023-26 Jim Easterbrook jim@jim-easterbrook.me.uk
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
Expand All @@ -23,6 +23,7 @@
%typemap(ret, fragment="private_data") return_type %{
if ($result != Py_None)
if (private_store_set($result, "refers_to", target)) {
Py_DECREF($result);
SWIG_fail;
}
%}
Expand Down
67 changes: 43 additions & 24 deletions src/interface/shared/pointer_store.i
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// python-exiv2 - Python interface to libexiv2
// http://github.com/jim-easterbrook/python-exiv2
// Copyright (C) 2023-25 Jim Easterbrook jim@jim-easterbrook.me.uk
// Copyright (C) 2023-26 Jim Easterbrook jim@jim-easterbrook.me.uk
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
Expand All @@ -25,15 +25,19 @@

#if SWIG_VERSION >= 0x040400
// Functions to store weak references to pointers (swig >= v4.4)
%fragment("pointer_store", "header", fragment="private_data") {
static void _process_list(PyObject* list, bool purge_only,
Exiv2::container_type::iterator* beg,
Exiv2::container_type::iterator* end) {
%fragment("pointer_store", "header", fragment="private_data",
fragment="weakref_getref") {
static int _process_list(PyObject* list, bool purge_only,
Exiv2::container_type::iterator* beg,
Exiv2::container_type::iterator* end) {
PyObject* py_ptr = NULL;
datum_type##_pointer* cpp_ptr = NULL;
for (Py_ssize_t idx = PyList_Size(list); idx > 0; idx--) {
py_ptr = PyWeakref_GetObject(PyList_GetItem(list, idx-1));
if (py_ptr == Py_None)
if (PyWeakref_GetRef(PyList_GetItem(list, idx-1), &py_ptr) < 0)
return -1;
if (py_ptr)
Py_DECREF(py_ptr);
else
goto forget;
if (purge_only)
continue;
Expand All @@ -52,35 +56,41 @@ forget:
PyList_SetSlice(list, idx-1, idx, NULL);
continue;
}
return 0;
};
static void purge_pointers(PyObject* list) {
_process_list(list, true, NULL, NULL);
static int purge_pointers(PyObject* list) {
return _process_list(list, true, NULL, NULL);
};
static void invalidate_pointers(PyObject* py_self) {
static int invalidate_pointers(PyObject* py_self) {
PyObject* list = private_store_get(py_self, "pointers");
if (list)
_process_list(list, false, NULL, NULL);
return _process_list(list, false, NULL, NULL);
return 0;
};
static void invalidate_pointers(PyObject* py_self,
Exiv2::container_type::iterator pos) {
static int invalidate_pointers(PyObject* py_self,
Exiv2::container_type::iterator pos) {
PyObject* list = private_store_get(py_self, "pointers");
if (list) {
Exiv2::container_type::iterator end = pos;
end++;
_process_list(list, false, &pos, &end);
return _process_list(list, false, &pos, &end);
}
return 0;
};
static void invalidate_pointers(PyObject* py_self,
Exiv2::container_type::iterator beg,
Exiv2::container_type::iterator end) {
static int invalidate_pointers(PyObject* py_self,
Exiv2::container_type::iterator beg,
Exiv2::container_type::iterator end) {
PyObject* list = private_store_get(py_self, "pointers");
if (list)
_process_list(list, false, &beg, &end);
return _process_list(list, false, &beg, &end);
return 0;
};
static int store_pointer(PyObject* py_self, PyObject* py_ptr) {
PyObject* list = private_store_get(py_self, "pointers");
if (list)
purge_pointers(list);
if (list) {
if (purge_pointers(list) < 0)
return -1;
}
else {
list = PyList_New(0);
if (!list)
Expand Down Expand Up @@ -114,21 +124,30 @@ static int store_pointer(PyObject* py_self, PyObject* py_ptr) {
#if SWIG_VERSION >= 0x040400
// clear() invalidates all pointers
%typemap(ret, fragment="pointer_store") void clear {
invalidate_pointers(self);
if (invalidate_pointers(self) < 0) {
Py_DECREF($result);
SWIG_fail;
}
}
// erase() and eraseFamily() invalidate some pointers
%typemap(check, fragment="pointer_store")
Exiv2::container_type::iterator pos {
invalidate_pointers(self, $1);
if (invalidate_pointers(self, $1) < 0) {
SWIG_fail;
}
}
%typemap(check, fragment="pointer_store")
(Exiv2::container_type::iterator beg,
Exiv2::container_type::iterator end) {
invalidate_pointers(self, $1, $2);
if (invalidate_pointers(self, $1, $2) < 0) {
SWIG_fail;
}
}
%typemap(check, fragment="pointer_store")
Exiv2::container_type::iterator& pos {
invalidate_pointers(self, *$1, arg1->end());
if (invalidate_pointers(self, *$1, arg1->end()) < 0) {
SWIG_fail;
}
}
#endif // SWIG_VERSION

Expand Down
27 changes: 23 additions & 4 deletions src/interface/shared/private_data.i
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// python-exiv2 - Python interface to libexiv2
// http://github.com/jim-easterbrook/python-exiv2
// Copyright (C) 2025 Jim Easterbrook jim@jim-easterbrook.me.uk
// Copyright (C) 2025-26 Jim Easterbrook jim@jim-easterbrook.me.uk
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -60,8 +60,24 @@ static int private_store_del(PyObject* py_self, const char* name) {
};
}

// Implementation of PyWeakref_GetRef for Python < 3.13
%fragment("weakref_getref", "header") {
%#if PY_VERSION_HEX < 0x030d0000
static int PyWeakref_GetRef(PyObject *ref, PyObject **pobj) {
*pobj = PyWeakref_GetObject(ref);
if (*pobj == Py_None) {
*pobj = NULL;
return 0;
}
Py_INCREF(*pobj);
return 1;
};
%#endif
}

// Functions to store references to memoryview objects and release them
%fragment("memoryview_funcs", "header", fragment="private_data") {
%fragment("memoryview_funcs", "header", fragment="private_data",
fragment="weakref_getref") {
static int store_view(PyObject* py_self, PyObject* view) {
PyObject* view_list = private_store_get(py_self, "view_list");
if (!view_list) {
Expand Down Expand Up @@ -92,9 +108,12 @@ static int release_views(PyObject* py_self) {
PyObject* view = NULL;
for (Py_ssize_t idx = PyList_Size(view_list); idx > 0; idx--) {
view_ref = PyList_GetItem(view_list, idx - 1);
view = PyWeakref_GetObject(view_ref);
if (view != Py_None)
if (PyWeakref_GetRef(view_ref, &view) < 0)
return -1;
if (view) {
Py_XDECREF(PyObject_CallMethod(view, "release", NULL));
Py_DECREF(view);
}
PyList_SetSlice(view_list, idx - 1, idx, NULL);
}
return 0;
Expand Down
Loading
Loading