Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
LeoHsiao1 committed Dec 2, 2020
2 parents 4d13dd6 + aca3323 commit f084bae
Show file tree
Hide file tree
Showing 15 changed files with 64 additions and 67 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/test_pip.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ jobs:
Test_run:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-18.04, macOS-latest, windows-2019]
python-version: [3.5, 3.6, 3.7, 3.8]
python-version: [3.5, 3.6, 3.7, 3.8, 3.9]
steps:
- name: git pull
uses: actions/checkout@v1
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/test_run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ jobs:
Test_run:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-18.04, macOS-latest, windows-2019]
python-version: [3.5, 3.6, 3.7, 3.8]
python-version: [3.5, 3.6, 3.7, 3.8, 3.9]
steps:
- name: git pull
uses: actions/checkout@v1
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Read/Write metadata of digital image, including [EXIF](https://en.wikipedia.org/
## Features

- Base on C++ API of [Exiv2](https://www.exiv2.org/index.html) and invoke it through [pybind11](https://github.com/pybind/pybind11).
- Supports running on Linux, MacOS and Windows, with Python3(64bit, including `3.5` `3.6` `3.7` `3.8`).
- Supports running on Linux, MacOS and Windows, with Python3(64bit, including `3.5` `3.6` `3.7` `3.8` `3.9`).
If you want to run pyexiv2 on another platform, please compile it yourself. See [lib](https://github.com/LeoHsiao1/pyexiv2/blob/master/pyexiv2/lib/README.md)
- [Supports various metadata](https://www.exiv2.org/metadata.html)
- [Supports various image formats](https://dev.exiv2.org/projects/exiv2/wiki/Supported_image_formats)
Expand Down
33 changes: 14 additions & 19 deletions docs/Tutorial-cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ class Image:
def read_xmp(self, encoding='utf-8') -> dict
def read_raw_xmp(self, encoding='utf-8') -> str

def modify_exif(self, dict_, encoding='utf-8')
def modify_iptc(self, dict_, encoding='utf-8')
def modify_xmp(self, dict_, encoding='utf-8')
def modify_exif(self, data: dict, encoding='utf-8')
def modify_iptc(self, data: dict, encoding='utf-8')
def modify_xmp(self, data: dict, encoding='utf-8')

def clear_exif(self)
def clear_iptc(self)
Expand All @@ -27,31 +27,29 @@ class ImageData(Image):
def __init__(self, data: bytes)
def get_bytes(self) -> bytes

set_log_level()
set_log_level(level=2)
```

## 类 Image

-`Image` 用于根据文件路径打开图片。例如:
```py
>>> from pyexiv2 import Image
>>> img = Image(r'.\pyexiv2\tests\1.jpg')
>>> import pyexiv2
>>> img = pyexiv2.Image(r'.\pyexiv2\tests\1.jpg')
>>> data = img.read_exif()
>>> img.close()
```
- 当你处理完图片之后,请记得调用 `img.close()` ,以释放用于存储图片数据的内存。不调用该方法会导致内存泄漏,但不会锁定文件描述符。
- 通过 `with` 关键字打开图片时,它会自动关闭图片。例如:
```py
with Image(r'.\pyexiv2\tests\1.jpg') as img:
with pyexiv2.Image(r'.\pyexiv2\tests\1.jpg') as img:
ims.read_exif()
```

### Image.read_*()

- 示例:
```py
>>> from pyexiv2 import Image
>>> img = Image(r'.\pyexiv2\tests\1.jpg')
>>> img.read_exif()
{'Exif.Image.DateTime': '2019:06:23 19:45:17', 'Exif.Image.Artist': 'TEST', 'Exif.Image.Rating': '4', ...}
>>> img.read_iptc()
Expand All @@ -63,20 +61,19 @@ set_log_level()
- pyexiv2 支持包含 Unicode 字符的图片路径、元数据。大部分函数都有一个默认参数:`encoding='utf-8'`
如果你因为图片路径、元数据包含非 ASCII 码字符而遇到错误,请尝试更换编码。例如:
```python
img = Image(path, encoding='utf-8')
img = Image(path, encoding='GBK')
img = Image(path, encoding='ISO-8859-1')
img = pyexiv2.Image(path, encoding='utf-8')
img = pyexiv2.Image(path, encoding='GBK')
img = pyexiv2.Image(path, encoding='ISO-8859-1')
```
另一个例子:中国地区的 Windows 电脑通常用 GBK 编码文件路径,因此它们不能被 utf-8 解码。
- 使用`Image.read_*()`是安全的。这些方法永远不会影响图片文件(md5不变)
- 使用 `Image.read_*()` 是安全的。这些方法永远不会影响图片文件(md5不变)
- 如果 XMP 元数据包含 `\v` 或 `\f`,它将被空格 ` ` 代替。
- 元数据的读取速度与元数据的数量成反比,不管图片的大小如何。

### Image.modify_*()

- 示例:
```py
>>> img = Image(r'.\pyexiv2\tests\1.jpg')
>>> # 准备要修改的XMP数据
>>> dict1 = {'Xmp.xmp.CreateDate': '2019-06-23T19:45:17.834', # 这将覆盖该标签的原始值,如果不存在该标签则将其添加
... 'Xmp.xmp.Rating': ''} # 赋值一个空字符串会删除该标签
Expand Down Expand Up @@ -124,20 +121,20 @@ set_log_level()
- 读取的示例:
```py
with open(r'.\pyexiv2\tests\1.jpg', 'rb') as f:
with ImageData(f.read()) as img:
with pyexiv2.ImageData(f.read()) as img:
data = img.read_exif()
```
- 修改的示例:
```py
with open(r'.\pyexiv2\tests\1.jpg', 'rb+') as f:
with ImageData(f.read()) as img:
with pyexiv2.ImageData(f.read()) as img:
changes = {'Iptc.Application2.ObjectName': 'test'}
img.modify_iptc(changes)
f.seek(0)
# 获取图片的字节数据并保存到文件中
f.write(img.get_bytes())
f.seek(0)
with ImageData(f.read()) as img:
with pyexiv2.ImageData(f.read()) as img:
result = img.read_iptc()
```

Expand Down Expand Up @@ -171,8 +168,6 @@ set_log_level()
- `error` 日志会被转换成异常并抛出,其它日志则会被打印到 stdout 。
- 调用函数 `pyexiv2.set_log_level()` 可以设置处理日志的级别。例如:
```py
>>> import pyexiv2
>>> img = pyexiv2.Image(r'.\pyexiv2\tests\1.jpg')
>>> img.modify_xmp({'Xmp.xmpMM.History': 'type="Seq"'})
RuntimeError: XMP Toolkit error 102: Indexing applied to non-array
Failed to encode XMP metadata.
Expand Down
31 changes: 13 additions & 18 deletions docs/Tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ class Image:
def read_xmp(self, encoding='utf-8') -> dict
def read_raw_xmp(self, encoding='utf-8') -> str

def modify_exif(self, dict_, encoding='utf-8')
def modify_iptc(self, dict_, encoding='utf-8')
def modify_xmp(self, dict_, encoding='utf-8')
def modify_exif(self, data: dict, encoding='utf-8')
def modify_iptc(self, data: dict, encoding='utf-8')
def modify_xmp(self, data: dict, encoding='utf-8')

def clear_exif(self)
def clear_iptc(self)
Expand All @@ -27,30 +27,29 @@ class ImageData(Image):
def __init__(self, data: bytes)
def get_bytes(self) -> bytes

set_log_level()
set_log_level(level=2)
```

## class Image

- Class `Image` is used to open an image based on the file path. For example:
```py
>>> from pyexiv2 import Image
>>> img = Image(r'.\pyexiv2\tests\1.jpg')
>>> import pyexiv2
>>> img = pyexiv2.Image(r'.\pyexiv2\tests\1.jpg')
>>> data = img.read_exif()
>>> img.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.
- Opening an image by keyword `with` will close the image automatically. For example:
```py
with Image(r'.\pyexiv2\tests\1.jpg') as img:
with pyexiv2.Image(r'.\pyexiv2\tests\1.jpg') as img:
data = ims.read_exif()
```

### Image.read_*()

- Sample:
```py
>>> img = Image(r'.\pyexiv2\tests\1.jpg')
>>> img.read_exif()
{'Exif.Image.DateTime': '2019:06:23 19:45:17', 'Exif.Image.Artist': 'TEST', 'Exif.Image.Rating': '4', ...}
>>> img.read_iptc()
Expand All @@ -67,15 +66,14 @@ set_log_level()
img = Image(path, encoding='ISO-8859-1')
```
Another example: Windows computers in China usually encoded file paths by GBK, so they cannot be decoded by utf-8.
- It is safe to use `Image.read_*()`. These methods never affect image files. (md5 unchanged)
- It is safe to use `Image.read_*()`. These methods never affect image files (md5 unchanged).
- If the XMP metadata contains `\v` or `\f`, it will be replaced with space ` `.
- The speed of reading metadata is inversely proportional to the amount of metadata, regardless of the size of the image.
### Image.modify_*()

- Sample:
```py
>>> img = Image(r'.\pyexiv2\tests\1.jpg')
>>> # Prepare the XMP data you want to modify
>>> dict1 = {'Xmp.xmp.CreateDate': '2019-06-23T19:45:17.834', # This will overwrite its original value, or add it if it doesn't exist
... 'Xmp.xmp.Rating': ''} # Set an empty str explicitly to delete the datum
Expand Down Expand Up @@ -123,20 +121,20 @@ set_log_level()
- Example of reading:
```py
with open(r'.\pyexiv2\tests\1.jpg', 'rb') as f:
with ImageData(f.read()) as img:
with pyexiv2.ImageData(f.read()) as img:
data = img.read_exif()
```
- Example of modifing:
```py
with open(r'.\pyexiv2\tests\1.jpg', 'rb+') as f:
with ImageData(f.read()) as img:
with pyexiv2.ImageData(f.read()) as img:
changes = {'Iptc.Application2.ObjectName': 'test'}
img.modify_iptc(changes)
f.seek(0)
# Get the bytes data of the image and save it to the file
f.write(img.get_bytes())
f.seek(0)
with ImageData(f.read()) as img:
with pyexiv2.ImageData(f.read()) as img:
result = img.read_iptc()
```

Expand Down Expand Up @@ -170,13 +168,10 @@ set_log_level()
- The `error` log will be converted to an exception and thrown. Other logs will be printed to stdout.
- Call the function `pyexiv2.set_log_level()` to set the level of handling logs. For example:
```py
>>> import pyexiv2
>>> img = pyexiv2.Image(r'.\pyexiv2\tests\1.jpg')
>>> img.modify_xmp({'Xmp.xmpMM.History': 'type="Seq"'})
>>> img.modify_xmp({'Xmp.xmpMM.History': 'type="Seq"'}) # An error that was not caught is displayed
RuntimeError: XMP Toolkit error 102: Indexing applied to non-array
Failed to encode XMP metadata.

>>> pyexiv2.set_log_level(4)
>>> img.modify_xmp({'Xmp.xmpMM.History': 'type="Seq"'})
>>> img.close()
>>> img.modify_xmp({'Xmp.xmpMM.History': 'type="Seq"'}) # No error displayed
```
4 changes: 2 additions & 2 deletions pyexiv2/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ def close(self):
""" Free the memory for storing image data. """
self.img.close_image()

# Disable all members
# Disable all methods and properties
def closed_warning():
raise RuntimeError('Do not operate on the closed image.')
raise RuntimeError('The image has been closed, so it is not allowed to operate.')
for attr in dir(self):
if not attr.startswith('__'):
if callable(getattr(self, attr)):
Expand Down
44 changes: 23 additions & 21 deletions pyexiv2/lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,65 +23,66 @@
- The current release version of Exiv2 is `0.27.2`.
- It is not necessary to compile all versions of exiv2api.cpp and save them to the git repository, except when release a new version.

## Compile steps on Darwin
## Compile steps on Linux

1. Download the release version of Exiv2, unpack it.
- Darwin : <https://www.exiv2.org/archive.html>
- Linux64 : <https://www.exiv2.org/archive.html>
- For example:
```sh
cd /Users/leo/Documents/
curl -O https://www.exiv2.org/builds/exiv2-0.27.2-Darwin.tar.gz
tar -zxvf exiv2-0.27.2-Darwin.tar.gz
cd /root/
curl -O https://www.exiv2.org/builds/exiv2-0.27.2-Linux64.tar.gz
tar -zxvf exiv2-0.27.2-Linux64.tar.gz
```

2. Prepare the environment:
```sh
EXIV2_DIR=/Users/leo/Documents/exiv2-0.27.2-Darwin
LIB_DIR=/Users/leo/Documents/pyexiv2/pyexiv2/lib/
cp ${EXIV2_DIR}/lib/libexiv2.0.27.2.dylib ${LIB_DIR}/libexiv2.dylib
EXIV2_DIR=/root/exiv2-0.27.2-Linux64 # According to your download location
LIB_DIR=/root/pyexiv2/pyexiv2/lib/
cp ${EXIV2_DIR}/lib/libexiv2.so.0.27.2 $LIB_DIR/libexiv2.so
```

3. Set up the python interpreter. For example:
```sh
py_version=8
# docker run -it --rm --name python3.$py_version -e "py_version=$py_version" -e "EXIV2_DIR=$EXIV2_DIR" -e "LIB_DIR=$LIB_DIR" -v /root:/root python:3.$py_version-buster sh
python3.$py_version -m pip install pybind11
```

4. Compile:
```sh
cd $LIB_DIR
g++ exiv2api.cpp -o py3${py_version}-darwin/exiv2api.so -O3 -Wall -std=c++11 -shared -fPIC `python3.$py_version -m pybind11 --includes` -I ${EXIV2_DIR}/include -L ${EXIV2_DIR}/lib -l exiv2 -undefined dynamic_lookup
g++ exiv2api.cpp -o py3${py_version}-linux/exiv2api.so -O3 -Wall -std=c++11 -shared -fPIC `python3.$py_version -m pybind11 --includes` -I ${EXIV2_DIR}/include -L ${EXIV2_DIR}/lib -l exiv2
```

## Compile steps on Linux
## Compile steps on Darwin

1. Download the release version of Exiv2, unpack it.
- Linux64 : <https://www.exiv2.org/archive.html>
- Darwin : <https://www.exiv2.org/archive.html>
- For example:
```sh
cd /root/pyexiv2/
curl -O https://www.exiv2.org/builds/exiv2-0.27.2-Linux64.tar.gz
tar -zxvf exiv2-0.27.2-Linux64.tar.gz
cd /Users/leo/Documents/
curl -O https://www.exiv2.org/builds/exiv2-0.27.2-Darwin.tar.gz
tar -zxvf exiv2-0.27.2-Darwin.tar.gz
```

2. Prepare the environment:
```sh
EXIV2_DIR=/root/pyexiv2/exiv2-0.27.2-Linux64 # According to your download location
LIB_DIR=/root/pyexiv2/pyexiv2/lib/
cp ${EXIV2_DIR}/lib/libexiv2.so.0.27.2 $LIB_DIR/libexiv2.so
EXIV2_DIR=/Users/leo/Documents/exiv2-0.27.2-Darwin
LIB_DIR=/Users/leo/Documents/pyexiv2/pyexiv2/lib/
cp ${EXIV2_DIR}/lib/libexiv2.0.27.2.dylib ${EXIV2_DIR}/lib/libexiv2.dylib
cp ${EXIV2_DIR}/lib/libexiv2.0.27.2.dylib ${LIB_DIR}/libexiv2.dylib
```

3. Set up the python interpreter. For example:
```sh
py_version=8
docker run -it --rm --name python3.$py_version -e "py_version=$py_version" -e "EXIV2_DIR=$EXIV2_DIR" -e "LIB_DIR=$LIB_DIR" -v /root/pyexiv2:/root/pyexiv2 python:3.$py_version bash
python3.$py_version -m pip install pybind11
```

4. Compile:
```sh
cd $LIB_DIR
g++ exiv2api.cpp -o py3${py_version}-linux/exiv2api.so -O3 -Wall -std=c++11 -shared -fPIC `python3.$py_version -m pybind11 --includes` -I ${EXIV2_DIR}/include -L ${EXIV2_DIR}/lib -l exiv2
g++ exiv2api.cpp -o py3${py_version}-darwin/exiv2api.so -O3 -Wall -std=c++11 -shared -fPIC `python3.$py_version -m pybind11 --includes` -I ${EXIV2_DIR}/include -L ${EXIV2_DIR}/lib -l exiv2 -undefined dynamic_lookup
```

## Compile steps on Windows
Expand All @@ -102,7 +103,8 @@
4. Compile:
```batch
set py_version=8
cl /MD /LD exiv2api.cpp /EHsc -I %EXIV2_DIR%\include -I C:\Users\Leo\AppData\Local\Programs\Python\Python3%py_version%\include /link %EXIV2_DIR%\lib\exiv2.lib C:\Users\Leo\AppData\Local\Programs\Python\Python3%py_version%\libs\python3%py_version%.lib /OUT:py3%py_version%-win\exiv2api.pyd
set py_home=C:\Users\Leo\AppData\Local\Programs\Python\Python3%py_version%
cl /MD /LD exiv2api.cpp /EHsc -I %EXIV2_DIR%\include -I %py_home%\include -I %py_home%\Lib\site-packages\pybind11\include /link %EXIV2_DIR%\lib\exiv2.lib %py_home%\libs\python3%py_version%.lib /OUT:py3%py_version%-win\exiv2api.pyd
del exiv2api.exp exiv2api.obj exiv2api.lib
```
Modify the path here according to your installation location.
- Modify the path here according to your installation location.
8 changes: 5 additions & 3 deletions pyexiv2/lib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
import platform


# Check the Python interpreter
if platform.architecture()[0] != '64bit':
raise RuntimeError('pyexiv2 can only run on 64-bit python3 interpreter.')

# Check the Python interpreter
py_version = platform.python_version()[:3]
if py_version not in ['3.5', '3.6', '3.7', '3.8']:
raise RuntimeError('pyexiv2 only supports these Python versions: 3.5, 3.6, 3.7, 3.8 , but your version is {} .'.format(py_version))
expected_py_version = ['3.5', '3.6', '3.7', '3.8', '3.9']
if py_version not in expected_py_version:
raise RuntimeError('pyexiv2 only supports these Python versions: {} . But your version is {} .'.format(expected_py_version, py_version))

lib_dir = os.path.dirname(__file__)

Expand Down
Empty file.
Binary file added pyexiv2/lib/py39-darwin/exiv2api.so
Binary file not shown.
Empty file.
Binary file added pyexiv2/lib/py39-linux/exiv2api.so
Binary file not shown.
Empty file.
Binary file added pyexiv2/lib/py39-win/exiv2api.pyd
Binary file not shown.
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

setuptools.setup(
name='pyexiv2',
version='2.3.1',
version='2.3.2',
author='LeoHsiao',
author_email='leohsiao@foxmail.com',
description='Read/Write metadata of digital image, including EXIF, IPTC, XMP.',
Expand All @@ -29,6 +29,7 @@
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
],
)

Expand Down

0 comments on commit f084bae

Please sign in to comment.