diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2c77f06..ffe618a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: matrix: os: [ubuntu-22.04] python_version: [3.7, 3.8, 3.9, '3.10', '3.11', '3.12'] - exiv2_version: [0.27.7] + exiv2_version: [0.28.1] runs-on: ${{matrix.os}} env: PLATFORM_NAME: linux @@ -88,7 +88,7 @@ jobs: matrix: os: [macos-12] python_version: [3.6, 3.7, 3.8, 3.9, '3.10', '3.11', '3.12'] - exiv2_version: [0.27.7] + exiv2_version: [0.28.1] runs-on: ${{matrix.os}} env: PLATFORM_NAME: darwin @@ -150,6 +150,7 @@ jobs: - name: Test run: | + brew install inih python -m pytest -v @@ -160,7 +161,7 @@ jobs: matrix: os: [windows-2019] python_version: [3.6, 3.7, 3.8, 3.9, '3.10', '3.11', '3.12'] - exiv2_version: [0.27.7] + exiv2_version: [0.28.1] runs-on: ${{matrix.os}} env: PLATFORM_NAME: win diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 25d02a5..f351bab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,12 +9,34 @@ on: workflow_dispatch: jobs: + job1: + name: Test on ubuntu + strategy: + fail-fast: false + matrix: + os: [ubuntu-22.04] + python_version: [3.7, 3.8, 3.9, '3.10', '3.11', '3.12'] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python_version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python_version }} + - name: Install dependencies + run: | + python -m pip install pytest psutil + - name: Test + run: | + pytest -v + + job2: name: Test strategy: fail-fast: false matrix: - os: [macos-12, macos-13, windows-2019] + os: [macos-12, macos-13] python_version: [3.6, 3.7, 3.8, 3.9, '3.10', '3.11', '3.12'] runs-on: ${{ matrix.os }} steps: @@ -28,15 +50,16 @@ jobs: python -m pip install pytest psutil - name: Test run: | + brew install inih pytest -v - job2: - name: Test on ubuntu + job3: + name: Test strategy: fail-fast: false matrix: - os: [ubuntu-22.04] - python_version: [3.7, 3.8, 3.9, '3.10', '3.11', '3.12'] + os: [windows-2019] + python_version: [3.6, 3.7, 3.8, 3.9, '3.10', '3.11', '3.12'] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/test_package.yml b/.github/workflows/test_package.yml index 043b3fa..fbac25f 100644 --- a/.github/workflows/test_package.yml +++ b/.github/workflows/test_package.yml @@ -9,11 +9,11 @@ on: jobs: job1: - name: Test package + name: Test package on ubuntu strategy: fail-fast: false matrix: - os: [macos-12, macos-13, windows-2019] + os: [ubuntu-22.04] python_version: [3.7, 3.8, 3.9, '3.10', '3.11', '3.12'] runs-on: ${{ matrix.os }} steps: @@ -24,7 +24,7 @@ jobs: python-version: ${{ matrix.python_version }} - name: Install dependencies run: | - python -m pip install pyexiv2==2.11.0 + python -m pip install pyexiv2==2.12.0 python -m pip install pytest psutil - name: Test run: | @@ -35,11 +35,38 @@ jobs: PYEXIV2_MODULE: pyexiv2 job2: - name: Test package on ubuntu + name: Test package strategy: fail-fast: false matrix: - os: [ubuntu-22.04] + os: [macos-12, macos-13] + python_version: [3.7, 3.8, 3.9, '3.10', '3.11', '3.12'] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python_version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python_version }} + - name: Install dependencies + run: | + python -m pip install pyexiv2==2.12.0 + python -m pip install pytest psutil + - name: Test + run: | + brew install inih + python -c "import os; os.remove('pyexiv2/__init__.py')" # Disable pyexiv2 in the repository, to use only the installed pyexiv2 + python -c "from pyexiv2 import Image; print(Image.__doc__)" + pytest -v + env: + PYEXIV2_MODULE: pyexiv2 + + job3: + name: Test package + strategy: + fail-fast: false + matrix: + os: [windows-2019] python_version: [3.7, 3.8, 3.9, '3.10', '3.11', '3.12'] runs-on: ${{ matrix.os }} steps: @@ -50,7 +77,7 @@ jobs: python-version: ${{ matrix.python_version }} - name: Install dependencies run: | - python -m pip install pyexiv2==2.11.0 + python -m pip install pyexiv2==2.12.0 python -m pip install pytest psutil - name: Test run: | diff --git a/.gitmodules b/.gitmodules index 10bc736..870c0c2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule ".github/exiv2"] path = .github/exiv2 url = https://github.com/Exiv2/exiv2.git - branch = v0.27.7 + branch = v0.28.1 diff --git a/docs/Tutorial-cn.md b/docs/Tutorial-cn.md index af78184..d9ffd9d 100644 --- a/docs/Tutorial-cn.md +++ b/docs/Tutorial-cn.md @@ -38,6 +38,12 @@ ``` - 这是因为 libintl.8.dylib 不存在。你需要执行 `brew install gettext` 。 +- 在 MacOS 上使用 pyexiv2 时,你可能遇到以下异常: + ```py + Library not loaded: '/usr/local/opt/inih/lib/libinih.0.dylib' + ``` + - 这是因为 libinih.0.dylib 不存在。你需要执行 `brew install inih` 。 + - 在 Windows 上使用 pyexiv2 时,你可能遇到以下异常: ```py >>> import pyexiv2 @@ -81,6 +87,10 @@ class Image: def clear_icc (self) def clear_thumbnail (self) + def copy_to_another_image(self, another_image, + exif=True, iptc=True, xmp=True, + comment=True, icc=True, thumbnail=True) + class ImageData(Image): def __init__(self, data: bytes) @@ -96,11 +106,11 @@ def convert_iptc_to_xmp(data: dict, encoding='utf-8') -> dict def convert_xmp_to_exif(data: dict, encoding='utf-8') -> dict def convert_xmp_to_iptc(data: dict, encoding='utf-8') -> dict -__version__ = '...' -__exiv2_version__ = '...' +__version__ = '2.12.0' +__exiv2_version__ = '0.28.1' ``` -## Class Image +## class Image - 类 `Image` 用于根据文件路径打开图片。例如: ```py @@ -118,7 +128,7 @@ __exiv2_version__ = '...' ``` - 另一个例子:中国地区的 Windows 电脑通常用 GBK 编码文件路径,因此它们不能被 utf-8 解码。 -### close +### close() - 当你处理完图片之后,请记得调用 `img.close()` ,以释放用于存储图片数据的内存。 - 不调用该方法会导致内存泄漏,但不会锁定文件描述符。 @@ -128,7 +138,7 @@ __exiv2_version__ = '...' data = img.read_exif() ``` -### read +### read_xx() - 读取元数据的示例: ```py @@ -144,7 +154,7 @@ __exiv2_version__ = '...' - 调用 `Image.read_*()` 是安全的。这些方法永远不会影响图片文件(md5不变)。 - 读取 XMP 元数据时,空白字符 `\v` 和 `\f` 会被替换为空格 ` ` 。 -### modify +### modify_xx() - 修改元数据的示例: ```py @@ -189,7 +199,7 @@ __exiv2_version__ = '...' '' ``` -### clear +### clear_xx() - 调用 `img.clear_exif()` 将删除图片的所有 EXIF 元数据。一旦清除元数据,pyexiv2 可能无法完全恢复它。 - `img.clear_iptc()` 和 `img.clear_xmp()` 的用法同理。 @@ -229,6 +239,30 @@ __exiv2_version__ = '...' - EXIF 标准允许在 JPEG 图片中嵌入缩略图,通常存储在 APP1 标签(FFE1)中。 - Exiv2 支持读写图像中的 EXIF 缩略图。但是只能插入 JPEG 缩略图(不能插入TIFF)。 +### copy_xx() + +- 以下代码用于从一个图片拷贝元数据到另一个图片: + ```py + with pyexiv2.Image(r'.\pyexiv2\tests\1.jpg') as img1: + with pyexiv2.Image(r'.\pyexiv2\tests\2.jpg') as img2: + img2.modify_exif(img1.read_exif()) + img2.modify_iptc(img1.read_iptc()) + img2.modify_xmp(img1.read_xmp()) + ``` + 但更推荐以下代码: + ```py + with pyexiv2.Image(r'.\pyexiv2\tests\1.jpg') as img1: + with pyexiv2.Image(r'.\pyexiv2\tests\2.jpg') as img2: + # 如果不想保留第二张图像中已有的元数据,可以提前清除第二张图像 + # img2.clear_exif() + # img2.clear_iptc() + # img2.clear_xmp() + img1.copy_to_another_image(img2, exif=True, iptc=True, xmp=True, comment=False, icc=False, thumbnail=False) + ``` + 理由如下: + - 它的效率更高,因为不需要多次执行 modify_xx() 。 + - 一张图片的元数据可能是错误的格式,无法被 pyexiv2 解析,但仍然可以复制到另一张图片上。 + ## class ImageData - 类 `ImageData` 继承于类 `Image`, 用于从字节数据中打开图片。 @@ -300,7 +334,7 @@ __exiv2_version__ = '...' ## convert -- Exiv2 支持将某些 EXIF 或 IPTC 标签,转换成 XMP 标签,也支持反向转换。参考: +- Exiv2 支持将某些 EXIF 或 IPTC 标签,转换成 XMP 标签,也支持反向转换。参考: - 示例: ```py >>> pyexiv2.convert_exif_to_xmp({'Exif.Image.Artist': 'test-中文-', 'Exif.Image.Rating': '4'}) diff --git a/docs/Tutorial.md b/docs/Tutorial.md index 4c21ced..ad95439 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -38,6 +38,12 @@ Language: [English](./Tutorial.md) | [中文](./Tutorial-cn.md) ``` - This is because libintl.8.dylib is missing. You need to execute `brew install gettext` . +- When using pyexiv2 on MacOS, you may encounter the following exception: + ```py + Library not loaded: '/usr/local/opt/inih/lib/libinih.0.dylib' + ``` + - This is because libinih.0.dylib is missing. You need to execute `brew install inih` . +1 - When using pyexiv2 on Windows, you may encounter the following exception: ```py >>> import pyexiv2 @@ -81,6 +87,10 @@ class Image: def clear_icc (self) def clear_thumbnail (self) + def copy_to_another_image(self, another_image, + exif=True, iptc=True, xmp=True, + comment=True, icc=True, thumbnail=True) + class ImageData(Image): def __init__(self, data: bytes) @@ -96,8 +106,8 @@ def convert_iptc_to_xmp(data: dict, encoding='utf-8') -> dict def convert_xmp_to_exif(data: dict, encoding='utf-8') -> dict def convert_xmp_to_iptc(data: dict, encoding='utf-8') -> dict -__version__ = '...' -__exiv2_version__ = '...' +__version__ = '2.12.0' +__exiv2_version__ = '0.28.1' ``` ## class Image @@ -118,7 +128,7 @@ __exiv2_version__ = '...' ``` - Another example: Windows computers in China usually encoded file paths by GBK, so they cannot be decoded by utf-8. -### close +### close() - When you're done with the image, remember to call `img.close()` to free the memory for storing image data. - Not calling this method causes a memory leak, but it doesn't lock the file descriptor. @@ -128,7 +138,7 @@ __exiv2_version__ = '...' data = img.read_exif() ``` -### read +### read_xx() - An example of reading metadata: ```py @@ -144,7 +154,7 @@ __exiv2_version__ = '...' - It is safe to call `Image.read_*()`. These methods never affect image files (md5 unchanged). - When reading XMP metadata, the whitespace characters `\v` and `\f` are replaced with the space ` `. -### modify +### modify_xx() - An example of modifing metadata: ```py @@ -189,7 +199,7 @@ __exiv2_version__ = '...' '' ``` -### clear +### clear_xx() - Calling `img.clear_exif()` will delete all EXIF metadata of the image. Once cleared, pyexiv2 may not be able to recover it completely. - Use `img.clear_iptc()` and `img.clear_xmp()` in the similar way. @@ -229,6 +239,30 @@ __exiv2_version__ = '...' - The EXIF standard allows embedding thumbnails in a JPEG image, which is typically stored in the APP1 tag (FFE1). - Exiv2 supports reading and writing EXIF thumbnails in images. But only JPEG thumbnails can be inserted (not TIFF thumbnails). +### copy_xx() + +- The following code is used to copy metadata from one image to another image: + ```py + with pyexiv2.Image(r'.\pyexiv2\tests\1.jpg') as img1: + with pyexiv2.Image(r'.\pyexiv2\tests\2.jpg') as img2: + img2.modify_exif(img1.read_exif()) + img2.modify_iptc(img1.read_iptc()) + img2.modify_xmp(img1.read_xmp()) + ``` + However, the following code is more recommended: + ```py + with pyexiv2.Image(r'.\pyexiv2\tests\1.jpg') as img1: + with pyexiv2.Image(r'.\pyexiv2\tests\2.jpg') as img2: + # If you don't want to keep the metadata already in the second image, you can clear the second image in advance. + # img2.clear_exif() + # img2.clear_iptc() + # img2.clear_xmp() + img1.copy_to_another_image(img2, exif=True, iptc=True, xmp=True, comment=False, icc=False, thumbnail=False) + ``` + The reasons are as follows: + - It's more efficient because it doesn't require multiple executions of modify_xx() . + - The metadata of an image may be in the wrong format and cannot be parsed by pyexiv2, but it can still be copied to another image. + ## class ImageData - Class `ImageData`, inherited from class `Image`, is used to open an image from bytes data. @@ -300,7 +334,7 @@ __exiv2_version__ = '...' ## convert -- Exiv2 supports converting some EXIF or IPTC tags to XMP tags, and also supports reverse conversion. Reference: +- Exiv2 supports converting some EXIF or IPTC tags to XMP tags, and also supports reverse conversion. Reference: - For example: ```py >>> pyexiv2.convert_exif_to_xmp({'Exif.Image.Artist': 'test-中文-', 'Exif.Image.Rating': '4'}) diff --git a/pyexiv2/__init__.py b/pyexiv2/__init__.py index 2118fa1..171f832 100644 --- a/pyexiv2/__init__.py +++ b/pyexiv2/__init__.py @@ -6,7 +6,7 @@ from .core import * -__version__ = '2.11.0' +__version__ = '2.12.0' __exiv2_version__ = exiv2api.version() diff --git a/pyexiv2/core.py b/pyexiv2/core.py index 8990893..e7ce966 100644 --- a/pyexiv2/core.py +++ b/pyexiv2/core.py @@ -9,7 +9,7 @@ class Image: Please call the public methods of this class. """ - def __init__(self, filename, encoding='utf-8'): + def __init__(self, filename: str, encoding='utf-8'): """ Open an image and load its metadata. """ self.img = exiv2api.Image(filename.encode(encoding)) @@ -80,7 +80,7 @@ def read_thumbnail(self) -> bytes: return self.img.read_thumbnail() def modify_exif(self, data: dict, encoding='utf-8'): - data = data.copy() # Avoid modifying the original data + data = data.copy() # Avoid modifying the original data when calling encode_ucs2() for tag in EXIF_TAGS_ENCODED_IN_UCS2: value = data.get(tag) if value: @@ -127,6 +127,17 @@ def clear_icc(self): def clear_thumbnail(self): self.img.clear_thumbnail() + def copy_to_another_image(self, another_image, + exif=True, iptc=True, xmp=True, + comment=True, icc=True, thumbnail=True): + """ Copy metadata from one image to another image. + """ + if not isinstance(another_image, (Image, ImageData)): + raise TypeError('The type of another_image should be pyexiv2.Image or pyexiv2.ImageData.') + self.img.copy_to_another_image(another_image.img, + exif, iptc, xmp, + comment, icc, thumbnail) + class ImageData(Image): """ diff --git a/pyexiv2/lib/README.md b/pyexiv2/lib/README.md index 8e6d9a2..aac1c24 100644 --- a/pyexiv2/lib/README.md +++ b/pyexiv2/lib/README.md @@ -27,16 +27,16 @@ 1. Download [the release of Exiv2](https://www.exiv2.org/archive.html) : ```sh - curl -O https://github.com/Exiv2/exiv2/releases/download/v0.27.7/exiv2-0.27.7-Linux64.tar.gz - tar -zxvf exiv2-0.27.7-Linux64.tar.gz + curl -O https://github.com/Exiv2/exiv2/releases/download/v0.28.1/exiv2-0.28.1-Linux64.tar.gz + tar -zxvf exiv2-0.28.1-Linux64.tar.gz ``` 2. Prepare environment variables according to your download path: ```sh - EXIV2_DIR=??/exiv2-0.27.7-Linux64 + EXIV2_DIR=??/exiv2-0.28.1-Linux64 LIB_DIR=??/pyexiv2/lib/ - cp $EXIV2_DIR/lib/libexiv2.so.0.27.7 $EXIV2_DIR/lib/libexiv2.so - cp $EXIV2_DIR/lib/libexiv2.so.0.27.7 $LIB_DIR/libexiv2.so + cp $EXIV2_DIR/lib/libexiv2.so.0.28.1 $EXIV2_DIR/lib/libexiv2.so + cp $EXIV2_DIR/lib/libexiv2.so.0.28.1 $LIB_DIR/libexiv2.so ``` 3. Prepare the python interpreter: @@ -65,15 +65,15 @@ 1. Download [the release of Exiv2](https://www.exiv2.org/archive.html) : ```sh - curl -O https://github.com/Exiv2/exiv2/releases/download/v0.27.7/exiv2-0.27.7-Darwin.tar.gz - tar -zxvf exiv2-0.27.7-Darwin.tar.gz + curl -O https://github.com/Exiv2/exiv2/releases/download/v0.28.1/exiv2-0.28.1-Darwin.tar.gz + tar -zxvf exiv2-0.28.1-Darwin.tar.gz ``` 2. Prepare environment variables according to your download path: ```sh - EXIV2_DIR=??/exiv2-0.27.7-Darwin + EXIV2_DIR=??/exiv2-0.28.1-Darwin LIB_DIR=??/pyexiv2/lib - cp ${EXIV2_DIR}/lib/libexiv2.0.27.7.dylib ${LIB_DIR}/libexiv2.dylib + cp ${EXIV2_DIR}/lib/libexiv2.0.28.1.dylib ${LIB_DIR}/libexiv2.dylib ``` 3. Prepare the python interpreter: @@ -102,8 +102,8 @@ 1. Download [the release of Exiv2](https://www.exiv2.org/archive.html) : ```sh - curl -O https://github.com/Exiv2/exiv2/releases/download/v0.27.7/exiv2-0.27.7-2019msvc64.zip - python -m zipfile -e exiv2-0.27.7-2019msvc64.zip . + curl -O https://github.com/Exiv2/exiv2/releases/download/v0.28.1/exiv2-0.28.1-2019msvc64.zip + python -m zipfile -e exiv2-0.28.1-2019msvc64.zip . ``` 2. Install `Visual Studio 2019` (must use the same version of Visual Studio as the Exiv2 build) , and set the environment variables it needs. @@ -111,7 +111,7 @@ 3. Prepare environment variables according to your download path: ```batch "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat" - set EXIV2_DIR=??\exiv2-0.27.7-2019msvc64 + set EXIV2_DIR=??\exiv2-0.28.1-2019msvc64 set LIB_DIR=??\pyexiv2\lib copy %EXIV2_DIR%\bin\exiv2.dll %LIB_DIR% ``` diff --git a/pyexiv2/lib/exiv2.dll b/pyexiv2/lib/exiv2.dll index bd030e3..8788cef 100644 Binary files a/pyexiv2/lib/exiv2.dll and b/pyexiv2/lib/exiv2.dll differ diff --git a/pyexiv2/lib/exiv2api.cpp b/pyexiv2/lib/exiv2api.cpp index 3be5fbb..72f2cc8 100644 --- a/pyexiv2/lib/exiv2api.cpp +++ b/pyexiv2/lib/exiv2api.cpp @@ -122,39 +122,39 @@ class Buffer{ class Image{ public: - Exiv2::Image::AutoPtr *img = new Exiv2::Image::AutoPtr; + Exiv2::Image::UniquePtr img; Image(const char *filename){ - *img = Exiv2::ImageFactory::open(filename); - if (img->get() == 0) - throw Exiv2::Error(Exiv2::kerErrorMessage, "Can not open this image."); - (*img)->readMetadata(); // Calling readMetadata() reads all types of metadata supported by the image + img = Exiv2::ImageFactory::open(filename); + if (img.get() == 0) + throw Exiv2::Error(Exiv2::ErrorCode::kerErrorMessage, "Can not open this image."); + img->readMetadata(); // Calling readMetadata() reads all types of metadata supported by the image check_error_log(); } Image(Buffer buffer){ - *img = Exiv2::ImageFactory::open((Exiv2::byte *)buffer.data, buffer.size); - if (img->get() == 0) - throw Exiv2::Error(Exiv2::kerErrorMessage, "Can not open this image."); - (*img)->readMetadata(); + img = Exiv2::ImageFactory::open((Exiv2::byte *)buffer.data, buffer.size); + if (img.get() == 0) + throw Exiv2::Error(Exiv2::ErrorCode::kerErrorMessage, "Can not open this image."); + img->readMetadata(); check_error_log(); } void close_image() { - delete img; + img.reset(); check_error_log(); } py::bytes get_bytes() { - Exiv2::BasicIo &io = (*img)->io(); + Exiv2::BasicIo &io = img->io(); return py::bytes((char *)io.mmap(), io.size()); } std::string get_mime_type() { - return (*img)->mimeType(); + return img->mimeType(); } py::dict get_access_mode() @@ -166,17 +166,17 @@ class Image{ enum AccessMode { amNone=0, amRead=1, amWrite=2, amReadWrite=3 }; */ auto mode = py::dict(); - mode["exif"] = int((*img)->checkMode(Exiv2::mdExif)); - mode["iptc"] = int((*img)->checkMode(Exiv2::mdIptc)); - mode["xmp"] = int((*img)->checkMode(Exiv2::mdXmp)); - mode["comment"] = int((*img)->checkMode(Exiv2::mdComment)); - // mode["icc"] = int((*img)->checkMode(Exiv2::mdIccProfile)); // Exiv2 will not check ICC + mode["exif"] = int(img->checkMode(Exiv2::mdExif)); + mode["iptc"] = int(img->checkMode(Exiv2::mdIptc)); + mode["xmp"] = int(img->checkMode(Exiv2::mdXmp)); + mode["comment"] = int(img->checkMode(Exiv2::mdComment)); + // mode["icc"] = int(img->checkMode(Exiv2::mdIccProfile)); // Exiv2 will not check ICC return mode; } py::object read_exif() { - Exiv2::ExifData &data = (*img)->exifData(); + Exiv2::ExifData &data = img->exifData(); Exiv2::ExifData::iterator i = data.begin(); Exiv2::ExifData::iterator end = data.end(); read_block; @@ -184,7 +184,7 @@ class Image{ py::object read_iptc() { - Exiv2::IptcData &data = (*img)->iptcData(); + Exiv2::IptcData &data = img->iptcData(); Exiv2::IptcData::iterator i = data.begin(); Exiv2::IptcData::iterator end = data.end(); read_block; @@ -192,7 +192,7 @@ class Image{ py::object read_xmp() { - Exiv2::XmpData &data = (*img)->xmpData(); + Exiv2::XmpData &data = img->xmpData(); Exiv2::XmpData::iterator i = data.begin(); Exiv2::XmpData::iterator end = data.end(); read_block; @@ -204,30 +204,30 @@ class Image{ When readMetadata() is called, Exiv2 reads the raw XMP text, stores it in a string called XmpPacket, then parses it into an XmpData instance. */ - return py::bytes((*img)->xmpPacket()); + return py::bytes(img->xmpPacket()); } py::object read_comment() { - return py::bytes((*img)->comment()); + return py::bytes(img->comment()); } py::object read_icc() { - Exiv2::DataBuf *buf = (*img)->iccProfile(); - return py::bytes((char*)buf->pData_, buf->size_); + Exiv2::DataBuf buf = img->iccProfile(); + return py::bytes((char*)buf.c_str(), buf.size()); } py::object read_thumbnail() { - Exiv2::ExifThumb exifThumb((*img)->exifData()); + Exiv2::ExifThumb exifThumb(img->exifData()); Exiv2::DataBuf buf = exifThumb.copy(); - return py::bytes((char*)buf.pData_, buf.size_); + return py::bytes((char*)buf.c_str(), buf.size()); } void modify_exif(py::list table, py::str encoding) { - Exiv2::ExifData &exifData = (*img)->exifData(); + Exiv2::ExifData &exifData = img->exifData(); // Iterate the input table. each line contains a key and a value for (auto _line : table){ @@ -239,30 +239,45 @@ class Image{ // Extract the fields in line std::string key = py::bytes(line[0].attr("encode")(encoding)); - std::string value = py::bytes(line[1].attr("encode")(encoding)); std::string typeName = py::bytes(line[2].attr("encode")(encoding)); // Locate the key Exiv2::ExifData::iterator key_pos = exifData.findKey(Exiv2::ExifKey(key)); - - // Delete the existing key to set a new value, otherwise the key may contain multiple values. - if (key_pos != exifData.end()) + // Use the while loop because the key may repeat + while (key_pos != exifData.end()){ + // Delete the existing key to set a new value exifData.erase(key_pos); + key_pos = exifData.findKey(Exiv2::ExifKey(key)); + } + // Set the value if (typeName == "_delete") continue; else if (typeName == "string") + { + std::string value = py::bytes(line[1].attr("encode")(encoding)); exifData[key] = value; + } + // The Exif specification allows for duplicate tags with the same key, although this is rare + else if (typeName == "array") + { + Exiv2::Value::UniquePtr value = Exiv2::Value::create(Exiv2::asciiString); + for (auto item: line[1]){ + std::string item_str = py::bytes(py::str(item).attr("encode")(encoding)); + value->read(item_str); + exifData.add(Exiv2::ExifKey(key), value.get()); + } + } } - (*img)->setExifData(exifData); // Save metadata to images in memory - (*img)->writeMetadata(); // Save metadata from memory to disk + img->setExifData(exifData); // Save metadata to images in memory + img->writeMetadata(); // Save metadata from memory to disk check_error_log(); } void modify_iptc(py::list table, py::str encoding) { - Exiv2::IptcData &iptcData = (*img)->iptcData(); + Exiv2::IptcData &iptcData = img->iptcData(); for (auto _line : table){ py::list line; for (auto field : _line) @@ -270,7 +285,7 @@ class Image{ std::string key = py::bytes(line[0].attr("encode")(encoding)); std::string typeName = py::bytes(line[2].attr("encode")(encoding)); Exiv2::IptcData::iterator key_pos = iptcData.findKey(Exiv2::IptcKey(key)); - while (key_pos != iptcData.end()){ // Use the while loop because the iptc key may repeat + while (key_pos != iptcData.end()){ iptcData.erase(key_pos); key_pos = iptcData.findKey(Exiv2::IptcKey(key)); } @@ -283,7 +298,7 @@ class Image{ } else if (typeName == "array") { - Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::string); + Exiv2::Value::UniquePtr value = Exiv2::Value::create(Exiv2::string); for (auto item: line[1]){ std::string item_str = py::bytes(py::str(item).attr("encode")(encoding)); value->read(item_str); @@ -291,14 +306,14 @@ class Image{ } } } - (*img)->setIptcData(iptcData); - (*img)->writeMetadata(); + img->setIptcData(iptcData); + img->writeMetadata(); check_error_log(); } void modify_xmp(py::list table, py::str encoding) { - Exiv2::XmpData &xmpData = (*img)->xmpData(); + Exiv2::XmpData &xmpData = img->xmpData(); for (auto _line : table){ py::list line; for (auto field : _line) @@ -306,8 +321,10 @@ class Image{ std::string key = py::bytes(line[0].attr("encode")(encoding)); std::string typeName = py::bytes(line[2].attr("encode")(encoding)); Exiv2::XmpData::iterator key_pos = xmpData.findKey(Exiv2::XmpKey(key)); - if (key_pos != xmpData.end()) + while (key_pos != xmpData.end()){ xmpData.erase(key_pos); + key_pos = xmpData.findKey(Exiv2::XmpKey(key)); + } if (typeName == "_delete") continue; else if (typeName == "string") @@ -317,7 +334,7 @@ class Image{ } else if (typeName == "array") { - Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::xmpSeq); + Exiv2::Value::UniquePtr value = Exiv2::Value::create(Exiv2::xmpSeq); for (auto item: line[1]){ std::string item_str = py::bytes(py::str(item).attr("encode")(encoding)); value->read(item_str); @@ -325,84 +342,114 @@ class Image{ xmpData.add(Exiv2::XmpKey(key), value.get()); } } - (*img)->setXmpData(xmpData); - (*img)->writeMetadata(); + img->setXmpData(xmpData); + img->writeMetadata(); check_error_log(); } void modify_raw_xmp(py::str data, py::str encoding) { std::string data_str = py::bytes(data.attr("encode")(encoding)); - (*img)->setXmpPacket(data_str); - (*img)->writeMetadata(); - (*img)->writeXmpFromPacket(); // Refresh the parsed XMP data in memory + img->setXmpPacket(data_str); + img->writeMetadata(); + (void)img->writeXmpFromPacket(); // Refresh the parsed XMP data in memory check_error_log(); } void modify_comment(py::str data, py::str encoding) { std::string data_str = py::bytes(data.attr("encode")(encoding)); - (*img)->setComment(data_str); - (*img)->writeMetadata(); + img->setComment(data_str); + img->writeMetadata(); check_error_log(); } void modify_icc(const char *data, long size) { Exiv2::DataBuf buf((Exiv2::byte *) data, size); - (*img)->setIccProfile(buf); - (*img)->writeMetadata(); + img->setIccProfile(std::move(buf)); + img->writeMetadata(); check_error_log(); } void modify_thumbnail(const char *data, long size) { - Exiv2::ExifThumb exifThumb((*img)->exifData()); + Exiv2::ExifThumb exifThumb(img->exifData()); exifThumb.setJpegThumbnail((Exiv2::byte *) data, size); - (*img)->writeMetadata(); + img->writeMetadata(); check_error_log(); } void clear_exif() { - (*img)->clearExifData(); - (*img)->writeMetadata(); + img->clearExifData(); + img->writeMetadata(); check_error_log(); } void clear_iptc() { - (*img)->clearIptcData(); - (*img)->writeMetadata(); + img->clearIptcData(); + img->writeMetadata(); check_error_log(); } void clear_xmp() { - (*img)->clearXmpData(); - (*img)->writeMetadata(); + img->clearXmpData(); + img->writeMetadata(); check_error_log(); } void clear_comment() { - (*img)->clearComment(); - (*img)->writeMetadata(); + img->clearComment(); + img->writeMetadata(); check_error_log(); } void clear_icc() { - (*img)->clearIccProfile(); - (*img)->writeMetadata(); + img->clearIccProfile(); + img->writeMetadata(); check_error_log(); } void clear_thumbnail() { - Exiv2::ExifThumb exifThumb((*img)->exifData()); + Exiv2::ExifThumb exifThumb(img->exifData()); exifThumb.erase(); - (*img)->writeMetadata(); + img->writeMetadata(); + check_error_log(); + } + + void copy_to_another_image(Image& another_image, + bool exif, bool iptc, bool xmp, + bool comment, bool icc, bool thumbnail) + { + if (comment) { + another_image.img->setComment(img->comment()); + } + if (icc) { + Exiv2::DataBuf buf = img->iccProfile(); + another_image.img->setIccProfile(std::move(buf)); + } + // Exif.Image.JPEGInterchangeFormat may be changed when modifying thumbnail, so copy thumbnail before copying exif. + if (thumbnail) { + Exiv2::ExifThumb exifThumb(another_image.img->exifData()); + Exiv2::DataBuf buf = exifThumb.copy(); + exifThumb.setJpegThumbnail(buf.c_data(), buf.size()); + } + if (exif) { + another_image.img->setExifData(img->exifData()); + } + if (iptc) { + another_image.img->setIptcData(img->iptcData()); + } + if (xmp) { + another_image.img->setXmpPacket(img->xmpPacket()); + } + another_image.img->writeMetadata(); check_error_log(); } @@ -464,7 +511,7 @@ py::object convert_iptc_to_xmp(py::list table, py::str encoding) } else if (typeName == "array") { - Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::string); + Exiv2::Value::UniquePtr value = Exiv2::Value::create(Exiv2::string); for (auto item: line[1]){ std::string item_str = py::bytes(py::str(item).attr("encode")(encoding)); value->read(item_str); @@ -504,7 +551,7 @@ py::object convert_xmp_to_exif(py::list table, py::str encoding) } else if (typeName == "array") { - Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::xmpSeq); + Exiv2::Value::UniquePtr value = Exiv2::Value::create(Exiv2::xmpSeq); for (auto item: line[1]){ std::string item_str = py::bytes(py::str(item).attr("encode")(encoding)); value->read(item_str); @@ -544,7 +591,7 @@ py::object convert_xmp_to_iptc(py::list table, py::str encoding) } else if (typeName == "array") { - Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::xmpSeq); + Exiv2::Value::UniquePtr value = Exiv2::Value::create(Exiv2::xmpSeq); for (auto item: line[1]){ std::string item_str = py::bytes(py::str(item).attr("encode")(encoding)); value->read(item_str); @@ -572,40 +619,41 @@ PYBIND11_MODULE(exiv2api, m) #endif m.def("set_log_level", &set_log_level); py::class_(m, "Buffer") - .def(py::init()) - .def_readonly("data" , &Buffer::data) - .def_readonly("size" , &Buffer::size) - .def("destroy" , &Buffer::destroy) - .def("dump" , &Buffer::dump); + .def(py::init()) + .def_readonly("data" , &Buffer::data) + .def_readonly("size" , &Buffer::size) + .def("destroy" , &Buffer::destroy) + .def("dump" , &Buffer::dump); py::class_(m, "Image") .def(py::init()) .def(py::init()) - .def("close_image" , &Image::close_image) - .def("get_bytes" , &Image::get_bytes) - .def("get_mime_type" , &Image::get_mime_type) - .def("get_access_mode" , &Image::get_access_mode) - .def("read_exif" , &Image::read_exif) - .def("read_iptc" , &Image::read_iptc) - .def("read_xmp" , &Image::read_xmp) - .def("read_raw_xmp" , &Image::read_raw_xmp) - .def("read_comment" , &Image::read_comment) - .def("read_icc" , &Image::read_icc) - .def("read_thumbnail" , &Image::read_thumbnail) - .def("modify_exif" , &Image::modify_exif) - .def("modify_iptc" , &Image::modify_iptc) - .def("modify_xmp" , &Image::modify_xmp) - .def("modify_raw_xmp" , &Image::modify_raw_xmp) - .def("modify_comment" , &Image::modify_comment) - .def("modify_icc" , &Image::modify_icc) - .def("modify_thumbnail" , &Image::modify_thumbnail) - .def("clear_exif" , &Image::clear_exif) - .def("clear_iptc" , &Image::clear_iptc) - .def("clear_xmp" , &Image::clear_xmp) - .def("clear_comment" , &Image::clear_comment) - .def("clear_icc" , &Image::clear_icc) - .def("clear_thumbnail" , &Image::clear_thumbnail); - m.def("convert_exif_to_xmp" , &convert_exif_to_xmp); - m.def("convert_iptc_to_xmp" , &convert_iptc_to_xmp); - m.def("convert_xmp_to_exif" , &convert_xmp_to_exif); - m.def("convert_xmp_to_iptc" , &convert_xmp_to_iptc); + .def("close_image" , &Image::close_image) + .def("get_bytes" , &Image::get_bytes) + .def("get_mime_type" , &Image::get_mime_type) + .def("get_access_mode" , &Image::get_access_mode) + .def("read_exif" , &Image::read_exif) + .def("read_iptc" , &Image::read_iptc) + .def("read_xmp" , &Image::read_xmp) + .def("read_raw_xmp" , &Image::read_raw_xmp) + .def("read_comment" , &Image::read_comment) + .def("read_icc" , &Image::read_icc) + .def("read_thumbnail" , &Image::read_thumbnail) + .def("modify_exif" , &Image::modify_exif) + .def("modify_iptc" , &Image::modify_iptc) + .def("modify_xmp" , &Image::modify_xmp) + .def("modify_raw_xmp" , &Image::modify_raw_xmp) + .def("modify_comment" , &Image::modify_comment) + .def("modify_icc" , &Image::modify_icc) + .def("modify_thumbnail" , &Image::modify_thumbnail) + .def("clear_exif" , &Image::clear_exif) + .def("clear_iptc" , &Image::clear_iptc) + .def("clear_xmp" , &Image::clear_xmp) + .def("clear_comment" , &Image::clear_comment) + .def("clear_icc" , &Image::clear_icc) + .def("clear_thumbnail" , &Image::clear_thumbnail) + .def("copy_to_another_image", &Image::copy_to_another_image); + m.def("convert_exif_to_xmp" , &convert_exif_to_xmp); + m.def("convert_iptc_to_xmp" , &convert_iptc_to_xmp); + m.def("convert_xmp_to_exif" , &convert_xmp_to_exif); + m.def("convert_xmp_to_iptc" , &convert_xmp_to_iptc); } diff --git a/pyexiv2/lib/libexiv2.dylib b/pyexiv2/lib/libexiv2.dylib index d548765..1a6af95 100644 Binary files a/pyexiv2/lib/libexiv2.dylib and b/pyexiv2/lib/libexiv2.dylib differ diff --git a/pyexiv2/lib/libexiv2.so b/pyexiv2/lib/libexiv2.so index b9039c0..6e50d2d 100644 Binary files a/pyexiv2/lib/libexiv2.so and b/pyexiv2/lib/libexiv2.so differ diff --git a/pyexiv2/lib/py3.10-darwin/exiv2api.so b/pyexiv2/lib/py3.10-darwin/exiv2api.so index 4b45fb0..17cfdca 100644 Binary files a/pyexiv2/lib/py3.10-darwin/exiv2api.so and b/pyexiv2/lib/py3.10-darwin/exiv2api.so differ diff --git a/pyexiv2/lib/py3.10-linux/exiv2api.so b/pyexiv2/lib/py3.10-linux/exiv2api.so index e163ed4..bca3f18 100644 Binary files a/pyexiv2/lib/py3.10-linux/exiv2api.so and b/pyexiv2/lib/py3.10-linux/exiv2api.so differ diff --git a/pyexiv2/lib/py3.10-win/exiv2api.pyd b/pyexiv2/lib/py3.10-win/exiv2api.pyd index e94faf0..a97d0a3 100644 Binary files a/pyexiv2/lib/py3.10-win/exiv2api.pyd and b/pyexiv2/lib/py3.10-win/exiv2api.pyd differ diff --git a/pyexiv2/lib/py3.11-darwin/exiv2api.so b/pyexiv2/lib/py3.11-darwin/exiv2api.so index fd47277..1a0bbdd 100644 Binary files a/pyexiv2/lib/py3.11-darwin/exiv2api.so and b/pyexiv2/lib/py3.11-darwin/exiv2api.so differ diff --git a/pyexiv2/lib/py3.11-linux/exiv2api.so b/pyexiv2/lib/py3.11-linux/exiv2api.so index 1ee07c7..e262e05 100644 Binary files a/pyexiv2/lib/py3.11-linux/exiv2api.so and b/pyexiv2/lib/py3.11-linux/exiv2api.so differ diff --git a/pyexiv2/lib/py3.11-win/exiv2api.pyd b/pyexiv2/lib/py3.11-win/exiv2api.pyd index c7f8e24..18bdb6b 100644 Binary files a/pyexiv2/lib/py3.11-win/exiv2api.pyd and b/pyexiv2/lib/py3.11-win/exiv2api.pyd differ diff --git a/pyexiv2/lib/py3.12-darwin/exiv2api.so b/pyexiv2/lib/py3.12-darwin/exiv2api.so index 64eee90..1d4ce8b 100644 Binary files a/pyexiv2/lib/py3.12-darwin/exiv2api.so and b/pyexiv2/lib/py3.12-darwin/exiv2api.so differ diff --git a/pyexiv2/lib/py3.12-linux/exiv2api.so b/pyexiv2/lib/py3.12-linux/exiv2api.so index caacd2d..d2603ed 100644 Binary files a/pyexiv2/lib/py3.12-linux/exiv2api.so and b/pyexiv2/lib/py3.12-linux/exiv2api.so differ diff --git a/pyexiv2/lib/py3.12-win/exiv2api.pyd b/pyexiv2/lib/py3.12-win/exiv2api.pyd index bd6b40c..fb49362 100644 Binary files a/pyexiv2/lib/py3.12-win/exiv2api.pyd and b/pyexiv2/lib/py3.12-win/exiv2api.pyd differ diff --git a/pyexiv2/lib/py3.6-darwin/exiv2api.so b/pyexiv2/lib/py3.6-darwin/exiv2api.so index c1bb17c..6310783 100644 Binary files a/pyexiv2/lib/py3.6-darwin/exiv2api.so and b/pyexiv2/lib/py3.6-darwin/exiv2api.so differ diff --git a/pyexiv2/lib/py3.6-win/exiv2api.pyd b/pyexiv2/lib/py3.6-win/exiv2api.pyd index 89cff83..f612cb9 100644 Binary files a/pyexiv2/lib/py3.6-win/exiv2api.pyd and b/pyexiv2/lib/py3.6-win/exiv2api.pyd differ diff --git a/pyexiv2/lib/py3.7-darwin/exiv2api.so b/pyexiv2/lib/py3.7-darwin/exiv2api.so index 8150e61..86975a1 100644 Binary files a/pyexiv2/lib/py3.7-darwin/exiv2api.so and b/pyexiv2/lib/py3.7-darwin/exiv2api.so differ diff --git a/pyexiv2/lib/py3.7-linux/exiv2api.so b/pyexiv2/lib/py3.7-linux/exiv2api.so index fb7c82e..66a2f29 100644 Binary files a/pyexiv2/lib/py3.7-linux/exiv2api.so and b/pyexiv2/lib/py3.7-linux/exiv2api.so differ diff --git a/pyexiv2/lib/py3.7-win/exiv2api.pyd b/pyexiv2/lib/py3.7-win/exiv2api.pyd index 679df2d..8272811 100644 Binary files a/pyexiv2/lib/py3.7-win/exiv2api.pyd and b/pyexiv2/lib/py3.7-win/exiv2api.pyd differ diff --git a/pyexiv2/lib/py3.8-darwin/exiv2api.so b/pyexiv2/lib/py3.8-darwin/exiv2api.so index b802ff9..110010e 100644 Binary files a/pyexiv2/lib/py3.8-darwin/exiv2api.so and b/pyexiv2/lib/py3.8-darwin/exiv2api.so differ diff --git a/pyexiv2/lib/py3.8-linux/exiv2api.so b/pyexiv2/lib/py3.8-linux/exiv2api.so index f84ec4a..e02e695 100644 Binary files a/pyexiv2/lib/py3.8-linux/exiv2api.so and b/pyexiv2/lib/py3.8-linux/exiv2api.so differ diff --git a/pyexiv2/lib/py3.8-win/exiv2api.pyd b/pyexiv2/lib/py3.8-win/exiv2api.pyd index 6bd0199..6c0efc8 100644 Binary files a/pyexiv2/lib/py3.8-win/exiv2api.pyd and b/pyexiv2/lib/py3.8-win/exiv2api.pyd differ diff --git a/pyexiv2/lib/py3.9-darwin/exiv2api.so b/pyexiv2/lib/py3.9-darwin/exiv2api.so index 8cf3469..81976d6 100644 Binary files a/pyexiv2/lib/py3.9-darwin/exiv2api.so and b/pyexiv2/lib/py3.9-darwin/exiv2api.so differ diff --git a/pyexiv2/lib/py3.9-linux/exiv2api.so b/pyexiv2/lib/py3.9-linux/exiv2api.so index 2b440ec..acf26f1 100644 Binary files a/pyexiv2/lib/py3.9-linux/exiv2api.so and b/pyexiv2/lib/py3.9-linux/exiv2api.so differ diff --git a/pyexiv2/lib/py3.9-win/exiv2api.pyd b/pyexiv2/lib/py3.9-win/exiv2api.pyd index 9412fe4..4ec32bb 100644 Binary files a/pyexiv2/lib/py3.9-win/exiv2api.pyd and b/pyexiv2/lib/py3.9-win/exiv2api.pyd differ diff --git a/pyexiv2/tests/base.py b/pyexiv2/tests/base.py index 962ee63..5023865 100644 --- a/pyexiv2/tests/base.py +++ b/pyexiv2/tests/base.py @@ -51,7 +51,7 @@ def diff_text(text1: (str, bytes), text2: (str, bytes)): def diff_dict(dict1, dict2): assert len(dict1) == len(dict2), "The two dict are of different length: {}, {}".format(len(dict1), len(dict2)) for k in dict1.keys(): - assert dict1[k] == dict2[k], "['{}'] is different.".format(k) + assert dict1[k] == dict2[k], "The two dict is different at ['{}'] :\n{}\n{}".format(k, dict1[k], dict2[k]) def check_img_md5(): diff --git a/pyexiv2/tests/data/1.jpg b/pyexiv2/tests/data/1.jpg index fdf6092..c8dc748 100644 Binary files a/pyexiv2/tests/data/1.jpg and b/pyexiv2/tests/data/1.jpg differ diff --git a/pyexiv2/tests/data/data.py b/pyexiv2/tests/data/data.py index eedebc2..efe12ac 100644 --- a/pyexiv2/tests/data/data.py +++ b/pyexiv2/tests/data/data.py @@ -14,13 +14,13 @@ 'Exif.Image.ImageDescription' : 'test-中文-', 'Exif.Image.Make' : 'test-中文-', 'Exif.Image.Model' : 'test-中文-', - 'Exif.Image.Orientation' : '1', + 'Exif.Image.Orientation' : ['1', '2', '3'], 'Exif.Image.DateTime' : '2019:08:12 19:44:04', 'Exif.Image.Artist' : 'test-中文-', 'Exif.Image.Rating' : '4', 'Exif.Image.RatingPercent' : '75', 'Exif.Image.Copyright' : 'test-中文-', - 'Exif.Image.ExifTag' : '2446', + 'Exif.Image.ExifTag' : '2470', 'Exif.Photo.ExposureProgram' : '1', 'Exif.Photo.ExifVersion' : '48 50 50 49', 'Exif.Photo.DateTimeOriginal' : '2019:08:12 19:44:04', @@ -42,7 +42,7 @@ 'Exif.Image.XPSubject' : 'test-中文-\x00', 'Exif.Image.0xea1c' : '28 234 0 0 0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0', 'Exif.Thumbnail.Compression' : '6', - 'Exif.Thumbnail.JPEGInterchangeFormat' : '4762', + 'Exif.Thumbnail.JPEGInterchangeFormat' : '4786', 'Exif.Thumbnail.JPEGInterchangeFormatLength': '6969', } diff --git a/pyexiv2/tests/test_func.py b/pyexiv2/tests/test_func.py index ac2eafc..60a7bd6 100644 --- a/pyexiv2/tests/test_func.py +++ b/pyexiv2/tests/test_func.py @@ -5,7 +5,7 @@ def test_version(): try: from .base import __exiv2_version__ - assert __exiv2_version__ == '0.27.7' + assert __exiv2_version__ == '0.28.1' except: ENV.skip_test = True raise @@ -220,6 +220,27 @@ def test_clear_thumbnail(): check_the_copy_of_img(diff_text, b'', 'read_thumbnail') +def test_copy_to_another_image(): + try: + shutil.copy(ENV.test_img, ENV.test_img_copy) + with Image(ENV.test_img_copy) as img_copy: + img_copy.clear_exif() + img_copy.clear_iptc() + img_copy.clear_xmp() + img_copy.clear_comment() + img_copy.clear_icc() + img_copy.clear_thumbnail() + ENV.img.copy_to_another_image(img_copy) + diff_dict(ENV.img.read_exif() , img_copy.read_exif()) + diff_dict(ENV.img.read_iptc() , img_copy.read_iptc()) + diff_dict(ENV.img.read_xmp() , img_copy.read_xmp()) + diff_text(ENV.img.read_comment() , img_copy.read_comment()) + diff_text(ENV.img.read_icc() , img_copy.read_icc()) + diff_text(ENV.img.read_thumbnail() , img_copy.read_thumbnail()) + finally: + os.remove(ENV.test_img_copy) + + def test_registerNs(): with pytest.raises(RuntimeError): ENV.img.modify_xmp({'Xmp.test.mytag1': 'Hello'}) diff --git a/pyexiv2/tests/test_perf.py b/pyexiv2/tests/test_perf.py index 614d58d..34e278f 100644 --- a/pyexiv2/tests/test_perf.py +++ b/pyexiv2/tests/test_perf.py @@ -36,6 +36,7 @@ def test_memory_leak_when_writing(): test_func.test_modify_xmp() test_func.test_modify_comment() test_func.test_modify_icc() + test_func.test_copy_to_another_image() memory_end = process.memory_info().rss delta = (memory_end - memory_1) / 1024 / 1024 assert delta < 1, 'Memory grew by {}MB, a memory leak may have occurred.'.format(delta) @@ -79,7 +80,7 @@ def test_transmit_various_characters(): def _test_thread_safe(): """ Test whether pyexiv2 can successfully run multiple threads. - TODO: pyexiv2 is not thread-safe because in exiv2api.cpp, check_error_log() reads and writes to a global variable. + TODO: pyexiv2 is not thread-safe because in exiv2api.cpp, check_error_log() reads and writes a global variable. """ import multiprocessing pool = multiprocessing.Pool(3) diff --git a/setup.py b/setup.py index d870e03..51dab4a 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setuptools.setup( name='pyexiv2', - version='2.11.0', # need to set the variable in 'pyexiv2/__init__.py' + version='2.12.0', # need to set the variable in 'pyexiv2/__init__.py' author='LeoHsiao', author_email='leohsiao@foxmail.com', description='Read/Write metadata(including EXIF, IPTC, XMP), comment and ICC Profile embedded in digital images.',