diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index da7d891..fd16614 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.28.1] + exiv2_version: [0.28.3] 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.28.1] + exiv2_version: [0.28.3] runs-on: ${{matrix.os}} env: PLATFORM_NAME: darwin @@ -161,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.28.1] + exiv2_version: [0.28.3] runs-on: ${{matrix.os}} env: PLATFORM_NAME: win diff --git a/.github/workflows/test_package.yml b/.github/workflows/test_package.yml index e90bc78..1b8c81a 100644 --- a/.github/workflows/test_package.yml +++ b/.github/workflows/test_package.yml @@ -24,7 +24,7 @@ jobs: python-version: ${{ matrix.python_version }} - name: Install dependencies run: | - python -m pip install pyexiv2==2.12.0 + python -m pip install pyexiv2==2.14.0 python -m pip install pytest psutil - name: Test run: | @@ -50,7 +50,7 @@ jobs: python-version: ${{ matrix.python_version }} - name: Install dependencies run: | - python -m pip install pyexiv2==2.12.0 + python -m pip install pyexiv2==2.14.0 python -m pip install pytest psutil - name: Test run: | @@ -77,7 +77,7 @@ jobs: python-version: ${{ matrix.python_version }} - name: Install dependencies run: | - python -m pip install pyexiv2==2.12.0 + python -m pip install pyexiv2==2.14.0 python -m pip install pytest psutil - name: Test run: | diff --git a/.gitmodules b/.gitmodules index 870c0c2..c57e3e9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule ".github/exiv2"] path = .github/exiv2 url = https://github.com/Exiv2/exiv2.git - branch = v0.28.1 + branch = v0.28.3 diff --git a/docs/Tutorial-cn.md b/docs/Tutorial-cn.md index 4293d44..7d202a6 100644 --- a/docs/Tutorial-cn.md +++ b/docs/Tutorial-cn.md @@ -65,8 +65,11 @@ class Image: def get_access_mode (self) -> dict def read_exif (self, encoding='utf-8') -> dict + def read_exif_detail(self, encoding='utf-8') -> dict def read_iptc (self, encoding='utf-8') -> dict + def read_iptc_detail(self, encoding='utf-8') -> dict def read_xmp (self, encoding='utf-8') -> dict + def read_xmp_detail (self, encoding='utf-8') -> dict def read_raw_xmp (self, encoding='utf-8') -> str def read_comment (self, encoding='utf-8') -> str def read_icc (self) -> bytes @@ -98,7 +101,6 @@ class ImageData(Image): def registerNs(namespace: str, prefix: str) -def enableBMFF(enable=True) def set_log_level(level=2) def convert_exif_to_xmp(data: dict, encoding='utf-8') -> dict @@ -106,8 +108,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__ = '2.12.0' -__exiv2_version__ = '0.28.1' +__version__ = '2.14.0' +__exiv2_version__ = '0.28.3' ``` ## class Image @@ -151,8 +153,9 @@ __exiv2_version__ = '0.28.1' {'Xmp.dc.format': 'image/jpeg', 'Xmp.dc.rights': 'lang="x-default" TEST', 'Xmp.dc.subject': 'TEST', ...} >>> img.close() ``` +- 上例只读取元数据的 `tag` 和 `value` 。你可以调用 `img.read_xx_detail()` 获取更多信息,包括 `typeName`、`tagDesc`、`tagLabel` 。 - 读取元数据的速度与元数据的数量成反比,不管图片的大小如何。 -- 调用 `Image.read_*()` 是安全的。这些方法永远不会影响图片文件(md5不变)。 +- 调用 `img.read_*()` 是安全的。这些方法永远不会影响图片文件(md5不变)。 - 读取 XMP 元数据时,空白字符 `\v` 和 `\f` 会被替换为空格 ` ` 。 ### modify_xx() @@ -307,11 +310,6 @@ __exiv2_version__ = '0.28.1' {'lang="x-default"': 'test-中文-', 'lang="de-DE"': 'Hallo, Welt'} ``` -## BMFF - -- 访问 BMFF 文件(CR3、HEIF、HEIC 和 AVIF)的功能默认是禁用的,可以通过调用 `pyexiv2.enableBMFF()` 来启用。 - > 注意:BMFF 文件可能涉及到专利权。pyexiv2 不负责识别任何此类专利权。pyexiv2 不对使用此代码所产生的法律后果负责。 - ## 日志 - Exiv2 有 5 种处理日志的级别: @@ -335,7 +333,7 @@ __exiv2_version__ = '0.28.1' ## 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 5b366c3..095d151 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -65,8 +65,11 @@ class Image: def get_access_mode (self) -> dict def read_exif (self, encoding='utf-8') -> dict + def read_exif_detail(self, encoding='utf-8') -> dict def read_iptc (self, encoding='utf-8') -> dict + def read_iptc_detail(self, encoding='utf-8') -> dict def read_xmp (self, encoding='utf-8') -> dict + def read_xmp_detail (self, encoding='utf-8') -> dict def read_raw_xmp (self, encoding='utf-8') -> str def read_comment (self, encoding='utf-8') -> str def read_icc (self) -> bytes @@ -98,7 +101,6 @@ class ImageData(Image): def registerNs(namespace: str, prefix: str) -def enableBMFF(enable=True) def set_log_level(level=2) def convert_exif_to_xmp(data: dict, encoding='utf-8') -> dict @@ -106,8 +108,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__ = '2.12.0' -__exiv2_version__ = '0.28.1' +__version__ = '2.14.0' +__exiv2_version__ = '0.28.3' ``` ## class Image @@ -151,8 +153,9 @@ __exiv2_version__ = '0.28.1' {'Xmp.dc.format': 'image/jpeg', 'Xmp.dc.rights': 'lang="x-default" TEST', 'Xmp.dc.subject': 'TEST', ...} >>> img.close() ``` +- The above example only reads the `tag` and `value` of the metadata. You can call `img.read_xx_detail()` to get more information, including `typeName`, `tagDesc`, `tagLabel`. - The speed of reading metadata is inversely proportional to the amount of metadata, regardless of the size of the image. -- It is safe to call `Image.read_*()`. These methods never affect image files (md5 unchanged). +- It is safe to call `img.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_xx() @@ -307,11 +310,6 @@ __exiv2_version__ = '0.28.1' {'lang="x-default"': 'test-中文-', 'lang="de-DE"': 'Hallo, Welt'} ``` -## BMFF - -- Access to BMFF files (CR3, HEIF, HEIC, and AVIF) is disabled by default, which can be enabled by calling `pyexiv2.enableBMFF()`. - > Attention: BMFF Support may be the subject of patent rights. pyexiv2 shall not be held responsible for identifying any such patent rights. pyexiv2 shall not be held responsible for the legal consequences of the use of this code. - ## Log - Exiv2 has five levels of handling logs: @@ -335,7 +333,7 @@ __exiv2_version__ = '0.28.1' ## 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 a04b1f4..fed78d8 100644 --- a/pyexiv2/__init__.py +++ b/pyexiv2/__init__.py @@ -6,7 +6,7 @@ from .core import * -__version__ = '2.12.0' +__version__ = '2.14.0' __exiv2_version__ = exiv2api.version() @@ -27,3 +27,9 @@ 'convert_xmp_to_exif', 'convert_xmp_to_iptc', ] + + +def ImageMetadata(*args, **kwargs): + raise NameError('ImageMetadata() is the API of py3exiv2, see https://pypi.org/project/py3exiv2 . ' + + 'However, the library you imported is pyexiv2, see https://pypi.org/project/pyexiv2 .') + diff --git a/pyexiv2/convert.py b/pyexiv2/convert.py index d8c5cdb..00b5cbd 100644 --- a/pyexiv2/convert.py +++ b/pyexiv2/convert.py @@ -35,10 +35,14 @@ def _parse(table: list, encoding='utf-8') -> dict: - """ Parse the metadata from a text table into a dict. """ - data = {} + """ + exiv2api is only responsible for returning the raw metadata, which is then parsed in Python: + """ + dic = {} for line in table: - tag, value, typeName = [field.decode(encoding) for field in line] + tag, value, typeName = line + tag = tag.decode(encoding) + value = value.decode(encoding) if typeName in ['XmpBag', 'XmpSeq']: value = value.split(', ') elif typeName in ['XmpText']: @@ -52,21 +56,39 @@ def _parse(table: list, encoding='utf-8') -> dict: value = {language: content for language, content in zip(fields[0::2], fields[1::2])} # Convert the values to a list of strings if the tag has multiple values - pre_value = data.get(tag) + pre_value = dic.get(tag) if pre_value == None: - data[tag] = value + dic[tag] = value elif isinstance(pre_value, str): - data[tag] = [pre_value, value] + dic[tag] = [pre_value, value] elif isinstance(pre_value, list): - data[tag].append(value) + dic[tag].append(value) - return data + return dic -def _dumps(data: dict) -> list: +def _parse_detail(raw_data: list, encoding='utf-8') -> dict: + table = [] + dic_detail = {} + for tag_detail in raw_data: + tag = tag_detail.pop('tag', b'') + value = tag_detail.pop('value', b'') + typeName = tag_detail.get('typeName', '') + table.append([tag, value, typeName]) + tag = tag.decode(encoding) + # A tag may be repeated, so avoid saving tag_detail twice + if not dic_detail.get(tag): + dic_detail[tag] = tag_detail + dic = _parse(table, encoding) + for tag in dic.keys(): + dic_detail[tag]['value'] = dic[tag] + return dic_detail + + +def _dumps(dic: dict) -> list: """ Convert the metadata from a dict into a text table. """ table = [] - for tag, value in data.items(): + for tag, value in dic.items(): tag = str(tag) if value == None: typeName = '_delete' @@ -135,4 +157,3 @@ def convert_xmp_to_iptc(data: dict, encoding='utf-8') -> dict: """ Input XMP metadata, convert to IPTC metadata and return. It works like executing modify_xmp() then read_iptc(). """ converted_data = exiv2api.convert_xmp_to_iptc(_dumps(data), encoding) return _parse(converted_data, encoding) - diff --git a/pyexiv2/core.py b/pyexiv2/core.py index e7ce966..e842e99 100644 --- a/pyexiv2/core.py +++ b/pyexiv2/core.py @@ -1,6 +1,6 @@ from .lib import exiv2api from .convert import * -from .convert import _parse, _dumps +from .convert import _parse, _parse_detail, _dumps class Image: @@ -11,7 +11,7 @@ class Image: def __init__(self, filename: str, encoding='utf-8'): """ Open an image and load its metadata. """ - self.img = exiv2api.Image(filename.encode(encoding)) + self._exiv2api_image = exiv2api.Image(filename.encode(encoding)) def __enter__(self): return self @@ -21,7 +21,7 @@ def __exit__(self, *args): def close(self): """ Free the memory for storing image data. """ - self.img.close_image() + self._exiv2api_image.close_image() # Disable all methods and properties def closed_warning(*args, **kwargs): @@ -35,7 +35,7 @@ def closed_warning(*args, **kwargs): def get_mime_type(self) -> str: """ Get the MIME type of the image, such as 'image/jpeg'. """ - return self.img.get_mime_type() + return self._exiv2api_image.get_mime_type() def get_access_mode(self) -> dict: """ Get the access mode to various metadata. """ @@ -43,20 +43,28 @@ def get_access_mode(self) -> dict: 1: 'read', 2: 'write', 3: 'read+write'} - dic = self.img.get_access_mode() + dic = self._exiv2api_image.get_access_mode() dic = {k:access_modes.get(v) for k,v in dic.items()} return dic def read_exif(self, encoding='utf-8') -> dict: - data = _parse(self.img.read_exif(), encoding) + data = _parse(self._exiv2api_image.read_exif(), encoding) for tag in EXIF_TAGS_ENCODED_IN_UCS2: value = data.get(tag) if value: data[tag] = decode_ucs2(value) return data + def read_exif_detail(self, encoding='utf-8') -> dict: + data = _parse_detail(self._exiv2api_image.read_exif_detail(), encoding) + for tag in EXIF_TAGS_ENCODED_IN_UCS2: + tag_detail = data.get(tag) + if tag_detail: + tag_detail['value'] = decode_ucs2(tag_detail['value']) + return data + def read_iptc(self, encoding='utf-8') -> dict: - data = _parse(self.img.read_iptc(), encoding) + data = _parse(self._exiv2api_image.read_iptc(), encoding) # For repeatable tags, the value is converted to list type even if there are no multiple values. for tag in IPTC_TAGS_REPEATABLE: value = data.get(tag) @@ -64,20 +72,32 @@ def read_iptc(self, encoding='utf-8') -> dict: data[tag] = [value] return data + def read_iptc_detail(self, encoding='utf-8') -> dict: + data = _parse_detail(self._exiv2api_image.read_iptc_detail(), encoding) + # For repeatable tags, the value is converted to list type even if there are no multiple values. + for tag in IPTC_TAGS_REPEATABLE: + tag_detail = data.get(tag) + if tag_detail and isinstance(tag_detail['value'], str): + tag_detail['value'] = [tag_detail['value']] + return data + def read_xmp(self, encoding='utf-8') -> dict: - return _parse(self.img.read_xmp(), encoding) + return _parse(self._exiv2api_image.read_xmp(), encoding) + + def read_xmp_detail(self, encoding='utf-8') -> dict: + return _parse_detail(self._exiv2api_image.read_xmp_detail(), encoding) def read_raw_xmp(self, encoding='utf-8') -> str: - return self.img.read_raw_xmp().decode(encoding) + return self._exiv2api_image.read_raw_xmp().decode(encoding) def read_comment(self, encoding='utf-8') -> str: - return self.img.read_comment().decode(encoding) + return self._exiv2api_image.read_comment().decode(encoding) def read_icc(self) -> bytes: - return self.img.read_icc() + return self._exiv2api_image.read_icc() def read_thumbnail(self) -> bytes: - return self.img.read_thumbnail() + return self._exiv2api_image.read_thumbnail() def modify_exif(self, data: dict, encoding='utf-8'): data = data.copy() # Avoid modifying the original data when calling encode_ucs2() @@ -85,47 +105,47 @@ def modify_exif(self, data: dict, encoding='utf-8'): value = data.get(tag) if value: data[tag] = encode_ucs2(value) - self.img.modify_exif(_dumps(data), encoding) + self._exiv2api_image.modify_exif(_dumps(data), encoding) def modify_iptc(self, data: dict, encoding='utf-8'): - self.img.modify_iptc(_dumps(data), encoding) + self._exiv2api_image.modify_iptc(_dumps(data), encoding) def modify_xmp(self, data: dict, encoding='utf-8'): - self.img.modify_xmp(_dumps(data), encoding) + self._exiv2api_image.modify_xmp(_dumps(data), encoding) def modify_raw_xmp(self, data: str, encoding='utf-8'): - self.img.modify_raw_xmp(data, encoding) + self._exiv2api_image.modify_raw_xmp(data, encoding) def modify_comment(self, data: str, encoding='utf-8'): - self.img.modify_comment(data, encoding) + self._exiv2api_image.modify_comment(data, encoding) def modify_icc(self, data: bytes): if not isinstance(data, bytes): raise TypeError('The ICC profile should be of bytes type.') - return self.img.modify_icc(data, len(data)) + return self._exiv2api_image.modify_icc(data, len(data)) def modify_thumbnail(self, data: bytes): if not isinstance(data, bytes): raise TypeError('The thumbnail should be of bytes type.') - return self.img.modify_thumbnail(data, len(data)) + return self._exiv2api_image.modify_thumbnail(data, len(data)) def clear_exif(self): - self.img.clear_exif() + self._exiv2api_image.clear_exif() def clear_iptc(self): - self.img.clear_iptc() + self._exiv2api_image.clear_iptc() def clear_xmp(self): - self.img.clear_xmp() + self._exiv2api_image.clear_xmp() def clear_comment(self): - self.img.clear_comment() + self._exiv2api_image.clear_comment() def clear_icc(self): - self.img.clear_icc() + self._exiv2api_image.clear_icc() def clear_thumbnail(self): - self.img.clear_thumbnail() + self._exiv2api_image.clear_thumbnail() def copy_to_another_image(self, another_image, exif=True, iptc=True, xmp=True, @@ -134,7 +154,7 @@ def copy_to_another_image(self, 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, + self._exiv2api_image.copy_to_another_image(another_image._exiv2api_image, exif, iptc, xmp, comment, icc, thumbnail) @@ -149,11 +169,11 @@ def __init__(self, data: bytes): if length >= 2**31: raise ValueError('Only images smaller than 2GB can be opened. The size of your image is {} bytes.'.format(length)) self.buffer = exiv2api.Buffer(data, length) - self.img = exiv2api.Image(self.buffer) + self._exiv2api_image = exiv2api.Image(self.buffer) def get_bytes(self) -> bytes: """ Get the bytes data of the image. """ - return self.img.get_bytes() + return self._exiv2api_image.get_bytes() def close(self): """ Free the memory for storing image data. """ @@ -171,7 +191,8 @@ def registerNs(namespace: str, prefix: str): def enableBMFF(enable=True): """ Enable or disable reading BMFF images. Return True on success. """ - return exiv2api.enableBMFF(enable) + print('[warning] enableBMFF() is deprecated since pyexiv2 v2.14.0 . Now it is always enabled.') + return True def set_log_level(level=2): diff --git a/pyexiv2/lib/README.md b/pyexiv2/lib/README.md index aac1c24..92fc12c 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.28.1/exiv2-0.28.1-Linux64.tar.gz - tar -zxvf exiv2-0.28.1-Linux64.tar.gz + curl -O https://github.com/Exiv2/exiv2/releases/download/v0.28.3/exiv2-0.28.3-Linux64.tar.gz + tar -zxvf exiv2-0.28.3-Linux64.tar.gz ``` 2. Prepare environment variables according to your download path: ```sh - EXIV2_DIR=??/exiv2-0.28.1-Linux64 + EXIV2_DIR=??/exiv2-0.28.3-Linux64 LIB_DIR=??/pyexiv2/lib/ - 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 + cp $EXIV2_DIR/lib/libexiv2.so.0.28.3 $EXIV2_DIR/lib/libexiv2.so + cp $EXIV2_DIR/lib/libexiv2.so.0.28.3 $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.28.1/exiv2-0.28.1-Darwin.tar.gz - tar -zxvf exiv2-0.28.1-Darwin.tar.gz + curl -O https://github.com/Exiv2/exiv2/releases/download/v0.28.3/exiv2-0.28.3-Darwin.tar.gz + tar -zxvf exiv2-0.28.3-Darwin.tar.gz ``` 2. Prepare environment variables according to your download path: ```sh - EXIV2_DIR=??/exiv2-0.28.1-Darwin + EXIV2_DIR=??/exiv2-0.28.3-Darwin LIB_DIR=??/pyexiv2/lib - cp ${EXIV2_DIR}/lib/libexiv2.0.28.1.dylib ${LIB_DIR}/libexiv2.dylib + cp ${EXIV2_DIR}/lib/libexiv2.0.28.3.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.28.1/exiv2-0.28.1-2019msvc64.zip - python -m zipfile -e exiv2-0.28.1-2019msvc64.zip . + curl -O https://github.com/Exiv2/exiv2/releases/download/v0.28.3/exiv2-0.28.3-2019msvc64.zip + python -m zipfile -e exiv2-0.28.3-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.28.1-2019msvc64 + set EXIV2_DIR=??\exiv2-0.28.3-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 8788cef..f5f0c3f 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 72f2cc8..dca118e 100644 --- a/pyexiv2/lib/exiv2api.cpp +++ b/pyexiv2/lib/exiv2api.cpp @@ -67,32 +67,20 @@ py::str version() return Exiv2::version(); } -// Ensure the current exiv2 version is equal to or greater than 0.27.4, which adds function Exiv2::enableBMFF(). -#if EXIV2_TEST_VERSION(0,27,4) -bool enableBMFF(bool enable) -{ - return Exiv2::enableBMFF(enable); -} -#endif - -#define read_block \ - { \ - py::list table; \ - for (; i != end; ++i) \ - { \ - py::list line; \ - line.append(py::bytes(i->key())); \ - \ - std::stringstream _value; \ - _value << i->value(); \ - line.append(py::bytes(_value.str())); \ - \ - const char *typeName = i->typeName(); \ - line.append(py::bytes((typeName ? typeName : "Unknown"))); \ - table.append(line); \ - } \ - check_error_log(); \ - return table; \ +// The result here should be stored by py::list, not py::dict. Because a tag can be repeated. +#define read_block \ + { \ + py::list result; \ + for (const auto &datum : data) \ + { \ + py::list line; \ + line.append(py::bytes(datum.key())); \ + line.append(py::bytes(datum.value().toString())); \ + line.append(py::str(datum.typeName())); \ + result.append(line); \ + } \ + check_error_log(); \ + return result; \ } class Buffer{ @@ -176,28 +164,78 @@ class Image{ py::object read_exif() { - Exiv2::ExifData &data = img->exifData(); - Exiv2::ExifData::iterator i = data.begin(); - Exiv2::ExifData::iterator end = data.end(); + Exiv2::ExifData &data = img->exifData(); read_block; } + py::object read_exif_detail() + { + Exiv2::ExifData &data = img->exifData(); + py::list result; + for (const auto &datum : data) + { + py::dict tag_detail = py::dict(); + tag_detail["tag"] = py::bytes(datum.key()); + tag_detail["idx"] = py::int_(datum.idx()); + tag_detail["ifdName"] = py::str(datum.ifdName()); + tag_detail["tagDesc"] = py::str(datum.tagDesc()); + tag_detail["tagLabel"] = py::str(datum.tagLabel()); + tag_detail["typeName"] = py::str(datum.typeName()); + tag_detail["value"] = py::bytes(datum.value().toString()); + result.append(tag_detail); + } + check_error_log(); + return result; + } + py::object read_iptc() { - Exiv2::IptcData &data = img->iptcData(); - Exiv2::IptcData::iterator i = data.begin(); - Exiv2::IptcData::iterator end = data.end(); + Exiv2::IptcData &data = img->iptcData(); read_block; } + py::object read_iptc_detail() + { + Exiv2::IptcData &data = img->iptcData(); + py::list result; + for (const auto &datum : data) + { + py::dict tag_detail = py::dict(); + tag_detail["tag"] = py::bytes(datum.key()); + tag_detail["tagDesc"] = py::str(datum.tagDesc()); + tag_detail["tagLabel"] = py::str(datum.tagLabel()); + tag_detail["typeName"] = py::str(datum.typeName()); + tag_detail["value"] = py::bytes(datum.value().toString()); + result.append(tag_detail); + } + check_error_log(); + return result; + } + py::object read_xmp() { - Exiv2::XmpData &data = img->xmpData(); - Exiv2::XmpData::iterator i = data.begin(); - Exiv2::XmpData::iterator end = data.end(); + Exiv2::XmpData &data = img->xmpData(); read_block; } + py::object read_xmp_detail() + { + Exiv2::XmpData &data = img->xmpData(); + py::list result; + for (const auto &datum : data) + { + py::dict tag_detail = py::dict(); + tag_detail["tag"] = py::bytes(datum.key()); + tag_detail["tagDesc"] = py::str(datum.tagDesc()); + tag_detail["tagLabel"] = py::str(datum.tagLabel()); + tag_detail["typeName"] = py::str(datum.typeName()); + tag_detail["value"] = py::bytes(datum.value().toString()); + result.append(tag_detail); + } + check_error_log(); + return result; + } + py::object read_raw_xmp() { /* @@ -458,7 +496,7 @@ class Image{ py::object convert_exif_to_xmp(py::list table, py::str encoding) { Exiv2::ExifData exifData; - Exiv2::XmpData xmpData; + Exiv2::XmpData data; // Write metadata, which works like modify_exif() for (auto _line : table){ @@ -479,16 +517,14 @@ py::object convert_exif_to_xmp(py::list table, py::str encoding) } // Convert and read metadata, which works like read_xmp() - Exiv2::copyExifToXmp(exifData, xmpData); - Exiv2::XmpData::iterator i = xmpData.begin(); - Exiv2::XmpData::iterator end = xmpData.end(); + Exiv2::copyExifToXmp(exifData, data); read_block; } py::object convert_iptc_to_xmp(py::list table, py::str encoding) { Exiv2::IptcData iptcData; - Exiv2::XmpData xmpData; + Exiv2::XmpData data; // Write metadata, which works like modify_iptc() for (auto _line : table){ @@ -521,16 +557,14 @@ py::object convert_iptc_to_xmp(py::list table, py::str encoding) } // Convert and read metadata, which works like read_xmp() - Exiv2::copyIptcToXmp(iptcData, xmpData); - Exiv2::XmpData::iterator i = xmpData.begin(); - Exiv2::XmpData::iterator end = xmpData.end(); + Exiv2::copyIptcToXmp(iptcData, data); read_block; } py::object convert_xmp_to_exif(py::list table, py::str encoding) { Exiv2::XmpData xmpData; - Exiv2::ExifData exifData; + Exiv2::ExifData data; // Write metadata, which works like modify_xmp() for (auto _line : table){ @@ -561,16 +595,14 @@ py::object convert_xmp_to_exif(py::list table, py::str encoding) } // Convert and read metadata, which works like read_exif() - Exiv2::copyXmpToExif(xmpData, exifData); - Exiv2::ExifData::iterator i = exifData.begin(); - Exiv2::ExifData::iterator end = exifData.end(); + Exiv2::copyXmpToExif(xmpData, data); read_block; } py::object convert_xmp_to_iptc(py::list table, py::str encoding) { Exiv2::XmpData xmpData; - Exiv2::IptcData iptcData; + Exiv2::IptcData data; // Write metadata, which works like modify_xmp() for (auto _line : table){ @@ -601,9 +633,7 @@ py::object convert_xmp_to_iptc(py::list table, py::str encoding) } // Convert and read metadata, which works like read_iptc() - Exiv2::copyXmpToIptc(xmpData, iptcData); - Exiv2::IptcData::iterator i = iptcData.begin(); - Exiv2::IptcData::iterator end = iptcData.end(); + Exiv2::copyXmpToIptc(xmpData, data); read_block; } @@ -614,9 +644,6 @@ PYBIND11_MODULE(exiv2api, m) m.def("init" , &init); m.def("version" , &version); m.def("registerNs" , &Exiv2::XmpProperties::registerNs); -#if EXIV2_TEST_VERSION(0,27,4) - m.def("enableBMFF" , &enableBMFF); -#endif m.def("set_log_level", &set_log_level); py::class_(m, "Buffer") .def(py::init()) @@ -632,8 +659,11 @@ PYBIND11_MODULE(exiv2api, m) .def("get_mime_type" , &Image::get_mime_type) .def("get_access_mode" , &Image::get_access_mode) .def("read_exif" , &Image::read_exif) + .def("read_exif_detail" , &Image::read_exif_detail) .def("read_iptc" , &Image::read_iptc) + .def("read_iptc_detail" , &Image::read_iptc_detail) .def("read_xmp" , &Image::read_xmp) + .def("read_xmp_detail" , &Image::read_xmp_detail) .def("read_raw_xmp" , &Image::read_raw_xmp) .def("read_comment" , &Image::read_comment) .def("read_icc" , &Image::read_icc) diff --git a/pyexiv2/lib/libexiv2.dylib b/pyexiv2/lib/libexiv2.dylib index 1a6af95..78a0aac 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 6e50d2d..9ca8376 100644 Binary files a/pyexiv2/lib/libexiv2.so and b/pyexiv2/lib/libexiv2.so differ diff --git a/pyexiv2/tests/data/data.py b/pyexiv2/tests/data/data.py index efe12ac..139feaa 100644 --- a/pyexiv2/tests/data/data.py +++ b/pyexiv2/tests/data/data.py @@ -1,6 +1,17 @@ import os +current_dir = os.path.dirname(__file__) + +with open(os.path.join(current_dir, 'gray.icc'), 'rb') as f: + GRAY_ICC = f.read() + +with open(os.path.join(current_dir, 'rgb.icc'), 'rb') as f: + RGB_ICC = f.read() + +with open(os.path.join(current_dir, '1-thumb.jpg'), 'rb') as f: + EXIF_THUMB = f.read() + MIME_TYPE = 'image/jpeg' ACCESS_MODE = { @@ -8,111 +19,584 @@ 'iptc' : 'read+write', 'xmp' : 'read+write', 'comment': 'read+write', - } +} -EXIF = { - 'Exif.Image.ImageDescription' : 'test-中文-', - 'Exif.Image.Make' : 'test-中文-', - 'Exif.Image.Model' : 'test-中文-', - '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' : '2470', - 'Exif.Photo.ExposureProgram' : '1', - 'Exif.Photo.ExifVersion' : '48 50 50 49', - 'Exif.Photo.DateTimeOriginal' : '2019:08:12 19:44:04', - 'Exif.Photo.DateTimeDigitized' : '2019:08:12 19:44:04', - 'Exif.Photo.LightSource' : '1', - 'Exif.Photo.SubSecTime' : '18', - 'Exif.Photo.SubSecTimeOriginal' : '18', - 'Exif.Photo.SubSecTimeDigitized': '176', - 'Exif.Photo.ColorSpace' : '65535', - 'Exif.Photo.WhiteBalance' : '0', - 'Exif.Photo.Contrast' : '0', - 'Exif.Photo.Saturation' : '0', - 'Exif.Photo.Sharpness' : '0', - 'Exif.Photo.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.Image.XPTitle' : 'test-中文-\x00', - 'Exif.Image.XPComment' : 'test-中文-\x00', - 'Exif.Image.XPAuthor' : 'test-中文-\x00', - 'Exif.Image.XPKeywords' : 'test-中文-\x00', - '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' : '4786', - 'Exif.Thumbnail.JPEGInterchangeFormatLength': '6969', +COMMENT = 'Hello World! \n你好!\n' + +EXIF_DETAIL = { + 'Exif.Image.ImageDescription': { + 'value': 'test-中文-', + 'typeName': 'Ascii', + 'idx': 1, + 'ifdName': 'IFD0', + 'tagDesc': 'A character string giving the title of the image. It may be a comment such as "1988 company picnic" or the like. Two-bytes character codes cannot be used. When a 2-bytes code is necessary, the Exif Private tag is to be used.', + 'tagLabel': 'Image Description' + }, + 'Exif.Image.Make': { + 'value': 'test-中文-', + 'typeName': 'Ascii', + 'idx': 2, + 'ifdName': 'IFD0', + 'tagDesc': 'The manufacturer of the recording equipment. This is the manufacturer of the DSC, scanner, video digitizer or other equipment that generated the image. When the field is left blank, it is treated as unknown.', + 'tagLabel': 'Manufacturer' + }, + 'Exif.Image.Model': { + 'value': 'test-中文-', + 'typeName': 'Ascii', + 'idx': 3, + 'ifdName': 'IFD0', + 'tagDesc': 'The model name or model number of the equipment. This is the model name or number of the DSC, scanner, video digitizer or other equipment that generated the image. When the field is left blank, it is treated as unknown.', + 'tagLabel': 'Model' + }, + 'Exif.Image.Orientation': { + 'value': ['1', '2', '3'], + 'typeName': 'Ascii', + 'idx': 4, + 'ifdName': 'IFD0', + 'tagDesc': 'The image orientation viewed in terms of rows and columns.', + 'tagLabel': 'Orientation' + }, + 'Exif.Image.DateTime': { + 'value': '2019:08:12 19:44:04', + 'typeName': 'Ascii', + 'idx': 7, + 'ifdName': 'IFD0', + 'tagDesc': 'The date and time of image creation. In Exif standard, it is the date and time the file was changed.', + 'tagLabel': 'Date and Time' + }, + 'Exif.Image.Artist': { + 'value': 'test-中文-', + 'typeName': 'Ascii', + 'idx': 8, + 'ifdName': 'IFD0', + 'tagDesc': 'This tag records the name of the camera owner, photographer or image creator. The detailed format is not specified, but it is recommended that the information be written as in the example below for ease of Interoperability. When the field is left blank, it is treated as unknown. Ex.) "Camera owner, John Smith; Photographer, Michael Brown; Image creator, Ken James"', + 'tagLabel': 'Artist' + }, + 'Exif.Image.Rating': { + 'value': '4', + 'typeName': 'Short', + 'idx': 9, + 'ifdName': 'IFD0', + 'tagDesc': 'Rating tag used by Windows', + 'tagLabel': 'Windows Rating' + }, + 'Exif.Image.RatingPercent': { + 'value': '75', + 'typeName': 'Short', + 'idx': 10, + 'ifdName': 'IFD0', + 'tagDesc': 'Rating tag used by Windows, value in percent', + 'tagLabel': 'Windows Rating Percent' + }, + 'Exif.Image.Copyright': { + 'value': 'test-中文-', + 'typeName': 'Ascii', + 'idx': 11, + 'ifdName': 'IFD0', + 'tagDesc': 'Copyright information. In this standard the tag is used to indicate both the photographer and editor copyrights. It is the copyright notice of the person or organization claiming rights to the image. The Interoperability copyright statement including date and rights should be written in this field; e.g., "Copyright, John Smith, 19xx. All rights reserved.". In this standard the field records both the photographer and editor copyrights, with each recorded in a separate part of the statement. When there is a clear distinction between the photographer and editor copyrights, these are to be written in the order of photographer followed by editor copyright, separated by NULL (in this case since the statement also ends with a NULL, there are two NULL codes). When only the photographer copyright is given, it is terminated by one NULL code. When only the editor copyright is given, the photographer copyright part consists of one space followed by a terminating NULL code, then the editor copyright is given. When the field is left blank, it is treated as unknown.', + 'tagLabel': 'Copyright' + }, + 'Exif.Image.ExifTag': { + 'value': '2470', + 'typeName': 'Long', + 'idx': 12, + 'ifdName': 'IFD0', + 'tagDesc': 'A pointer to the Exif IFD. Interoperability, Exif IFD has the same structure as that of the IFD specified in TIFF. ordinarily, however, it does not contain image data as in the case of TIFF.', + 'tagLabel': 'Exif IFD Pointer' + }, + 'Exif.Photo.ExposureProgram': { + 'value': '1', + 'typeName': 'Short', + 'idx': 1, + 'ifdName': 'Exif', + 'tagDesc': 'The class of the program used by the camera to set exposure when the picture is taken.', + 'tagLabel': 'Exposure Program' + }, + 'Exif.Photo.ExifVersion': { + 'value': '48 50 50 49', + 'typeName': 'Undefined', + 'idx': 2, + 'ifdName': 'Exif', + 'tagDesc': 'The version of this standard supported. Nonexistence of this field is taken to mean nonconformance to the standard.', + 'tagLabel': 'Exif Version' + }, + 'Exif.Photo.DateTimeOriginal': { + 'value': '2019:08:12 19:44:04', + 'typeName': 'Ascii', + 'idx': 3, + 'ifdName': 'Exif', + 'tagDesc': 'The date and time when the original image data was generated. For a digital still camera the date and time the picture was taken are recorded.', + 'tagLabel': 'Date and Time (original)' + }, + 'Exif.Photo.DateTimeDigitized': { + 'value': '2019:08:12 19:44:04', + 'typeName': 'Ascii', + 'idx': 4, + 'ifdName': 'Exif', + 'tagDesc': 'The date and time when the image was stored as digital data.', + 'tagLabel': 'Date and Time (digitized)' + }, + 'Exif.Photo.LightSource': { + 'value': '1', + 'typeName': 'Short', + 'idx': 5, + 'ifdName': 'Exif', + 'tagDesc': 'The kind of light source.', + 'tagLabel': 'Light Source' + }, + 'Exif.Photo.SubSecTime': { + 'value': '18', + 'typeName': 'Ascii', + 'idx': 6, + 'ifdName': 'Exif', + 'tagDesc': 'A tag used to record fractions of seconds for the tag.', + 'tagLabel': 'Sub-seconds Time' + }, + 'Exif.Photo.SubSecTimeOriginal': { + 'value': '18', + 'typeName': 'Ascii', + 'idx': 7, + 'ifdName': 'Exif', + 'tagDesc': 'A tag used to record fractions of seconds for the tag.', + 'tagLabel': 'Sub-seconds Time Original' + }, + 'Exif.Photo.SubSecTimeDigitized': { + 'value': '176', + 'typeName': 'Ascii', + 'idx': 8, + 'ifdName': 'Exif', + 'tagDesc': 'A tag used to record fractions of seconds for the tag.', + 'tagLabel': 'Sub-seconds Time Digitized' + }, + 'Exif.Photo.ColorSpace': { + 'value': '65535', + 'typeName': 'Short', + 'idx': 9, + 'ifdName': 'Exif', + 'tagDesc': 'The color space information tag is always recorded as the color space specifier. Normally sRGB is used to define the color space based on the PC monitor conditions and environment. If a color space other than sRGB is used, Uncalibrated is set. Image data recorded as Uncalibrated can be treated as sRGB when it is converted to FlashPix.', + 'tagLabel': 'Color Space' + }, + 'Exif.Photo.WhiteBalance': { + 'value': '0', + 'typeName': 'Short', + 'idx': 10, + 'ifdName': 'Exif', + 'tagDesc': 'This tag indicates the white balance mode set when the image was shot.', + 'tagLabel': 'White Balance' + }, + 'Exif.Photo.Contrast': { + 'value': '0', + 'typeName': 'Short', + 'idx': 11, + 'ifdName': 'Exif', + 'tagDesc': 'This tag indicates the direction of contrast processing applied by the camera when the image was shot.', + 'tagLabel': 'Contrast' + }, + 'Exif.Photo.Saturation': { + 'value': '0', + 'typeName': 'Short', + 'idx': 12, + 'ifdName': 'Exif', + 'tagDesc': 'This tag indicates the direction of saturation processing applied by the camera when the image was shot.', + 'tagLabel': 'Saturation' + }, + 'Exif.Photo.Sharpness': { + 'value': '0', + 'typeName': 'Short', + 'idx': 13, + 'ifdName': 'Exif', + 'tagDesc': 'This tag indicates the direction of sharpness processing applied by the camera when the image was shot.', + 'tagLabel': 'Sharpness' + }, + 'Exif.Photo.0xea1c': { + 'value': '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', + 'typeName': 'Undefined', + 'idx': 14, + 'ifdName': 'Exif', + 'tagDesc': '', + 'tagLabel': '' + }, + 'Exif.Image.XPTitle': { + 'value': 'test-中文-\x00', + 'typeName': 'Byte', + 'idx': 13, + 'ifdName': 'IFD0', + 'tagDesc': 'Title tag used by Windows, encoded in UCS2', + 'tagLabel': 'Windows Title' + }, + 'Exif.Image.XPComment': { + 'value': 'test-中文-\x00', + 'typeName': 'Byte', + 'idx': 14, + 'ifdName': 'IFD0', + 'tagDesc': 'Comment tag used by Windows, encoded in UCS2', + 'tagLabel': 'Windows Comment' + }, + 'Exif.Image.XPAuthor': { + 'value': 'test-中文-\x00', + 'typeName': 'Byte', + 'idx': 15, + 'ifdName': 'IFD0', + 'tagDesc': 'Author tag used by Windows, encoded in UCS2', + 'tagLabel': 'Windows Author' + }, + 'Exif.Image.XPKeywords': { + 'value': 'test-中文-\x00', + 'typeName': 'Byte', + 'idx': 16, + 'ifdName': 'IFD0', + 'tagDesc': 'Keywords tag used by Windows, encoded in UCS2', + 'tagLabel': 'Windows Keywords' + }, + 'Exif.Image.XPSubject': { + 'value': 'test-中文-\x00', + 'typeName': 'Byte', + 'idx': 17, + 'ifdName': 'IFD0', + 'tagDesc': 'Subject tag used by Windows, encoded in UCS2', + 'tagLabel': 'Windows Subject' + }, + 'Exif.Image.0xea1c': { + 'value': '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', + 'typeName': 'Undefined', + 'idx': 18, + 'ifdName': 'IFD0', + 'tagDesc': '', + 'tagLabel': '' + }, + 'Exif.Thumbnail.Compression': { + 'value': '6', + 'typeName': 'Short', + 'idx': 1, + 'ifdName': 'IFD1', + 'tagDesc': 'The compression scheme used for the image data. When a primary image is JPEG compressed, this designation is not necessary and is omitted. When thumbnails use JPEG compression, this tag value is set to 6.', + 'tagLabel': 'Compression' + }, + 'Exif.Thumbnail.JPEGInterchangeFormat': { + 'value': '4786', + 'typeName': 'Long', + 'idx': 2, + 'ifdName': 'IFD1', + 'tagDesc': 'The offset to the start byte (SOI) of JPEG compressed thumbnail data. This is not used for primary image JPEG data.', + 'tagLabel': 'JPEG Interchange Format' + }, + 'Exif.Thumbnail.JPEGInterchangeFormatLength': { + 'value': '6969', + 'typeName': 'Long', + 'idx': 3, + 'ifdName': 'IFD1', + 'tagDesc': 'The number of bytes of JPEG compressed thumbnail data. This is not used for primary image JPEG data. JPEG thumbnails are not divided but are recorded as a continuous JPEG bitstream from SOI to EOI. Appn and COM markers should not be recorded. Compressed thumbnails must be recorded in no more than 64 Kbytes, including all other data to be recorded in APP1.', + 'tagLabel': 'JPEG Interchange Format Length' } +} + +EXIF = {tag:tag_detail['value'] for tag,tag_detail in EXIF_DETAIL.items()} -IPTC = { - 'Iptc.Envelope.CharacterSet' : '\x1b%G', - 'Iptc.Application2.RecordVersion': '4', - 'Iptc.Application2.ObjectName' : 'test-中文-', - 'Iptc.Application2.Keywords' : ['tag1', 'tag2', 'tag3'], - 'Iptc.Application2.DateCreated' : '2019-08-12', - 'Iptc.Application2.TimeCreated' : '19:44:04+00:00', - 'Iptc.Application2.Byline' : ['test-中文-'], - 'Iptc.Application2.Copyright' : 'test-中文-', - 'Iptc.Application2.Caption' : 'test-中文-', +IPTC_DETAIL = { + 'Iptc.Envelope.CharacterSet': { + 'value': '\x1b%G', + 'typeName': 'String', + 'tagDesc': 'This tag consisting of one or more control functions used for the announcement, invocation or designation of coded character sets. The control functions follow the ISO 2022 standard and may consist of the escape control character and one or more graphic characters.', + 'tagLabel': 'Character Set' + }, + 'Iptc.Application2.RecordVersion': { + 'value': '4', + 'typeName': 'Short', + 'tagDesc': 'A binary number identifying the version of the Information Interchange Model, Part II, utilised by the provider. Version numbers are assigned by IPTC and NAA organizations.', + 'tagLabel': 'Record Version' + }, + 'Iptc.Application2.ObjectName': { + 'value': 'test-中文-', + 'typeName': 'String', + 'tagDesc': 'Used as a shorthand reference for the object. Changes to exist-ing data, such as updated stories or new crops on photos, should be identified in tag .', + 'tagLabel': 'Object Name' + }, + 'Iptc.Application2.Keywords': { + 'value': ['tag1', 'tag2', 'tag3'], + 'typeName': 'String', + 'tagDesc': 'Used to indicate specific information retrieval words. It is expected that a provider of various types of data that are related in subject matter uses the same keyword, enabling the receiving system or subsystems to search across all types of data for related material.', + 'tagLabel': 'Keywords' + }, + 'Iptc.Application2.DateCreated': { + 'value': '2019-08-12', + 'typeName': 'Date', + 'tagDesc': 'Represented in the form CCYYMMDD to designate the date the intellectual content of the object data was created rather than the date of the creation of the physical representation. Follows ISO 8601 standard.', + 'tagLabel': 'Date Created' + }, + 'Iptc.Application2.TimeCreated': { + 'value': '19:44:04+00:00', + 'typeName': 'Time', + 'tagDesc': 'Represented in the form HHMMSS:HHMM to designate the time the intellectual content of the object data current source material was created rather than the creation of the physical representation. Follows ISO 8601 standard.', + 'tagLabel': 'Time Created' + }, + 'Iptc.Application2.Byline': { + 'value': ['test-中文-'], + 'typeName': 'String', + 'tagDesc': 'Contains name of the creator of the object data, e.g. writer, photographer or graphic artist.', + 'tagLabel': 'By-line' + }, + 'Iptc.Application2.Copyright': { + 'value': 'test-中文-', + 'typeName': 'String', + 'tagDesc': 'Contains any necessary copyright notice.', + 'tagLabel': 'Copyright' + }, + 'Iptc.Application2.Caption': { + 'value': 'test-中文-', + 'typeName': 'String', + 'tagDesc': 'A textual description of the object data.', + 'tagLabel': 'Caption' } +} -XMP = { - 'Xmp.dc.format' : 'image/jpeg', - 'Xmp.dc.title' : {'lang="x-default"': 'test-中文-', 'lang="de-DE"': 'Hallo, Welt'}, - 'Xmp.dc.subject' : ['tag1', 'tag2', 'tag3'], - 'Xmp.dc.creator' : ['test-中文-'], - 'Xmp.dc.rights' : {'lang="x-default"': 'test-中文-'}, - 'Xmp.dc.description' : {'lang="x-default"': 'test-中文-'}, - 'Xmp.xmp.Rating' : '4', - 'Xmp.xmp.CreateDate' : '2019-08-12T19:44:04.176', - 'Xmp.xmp.ModifyDate' : '2019-08-12T19:44:04.18', - 'Xmp.xmp.MetadataDate' : '2020-04-06T00:55:07+08:00', - 'Xmp.MicrosoftPhoto.Rating' : '75', - 'Xmp.MicrosoftPhoto.DateAcquired' : '2019-08-12T19:44:08.151', - 'Xmp.MicrosoftPhoto.LensModel' : 'test-中文-', - 'Xmp.MicrosoftPhoto.LensManufacturer' : 'test-中文-', - 'Xmp.MicrosoftPhoto.FlashModel' : 'test-中文-', - 'Xmp.MicrosoftPhoto.FlashManufacturer' : 'test-中文-', - 'Xmp.MicrosoftPhoto.CameraSerialNumber' : 'test-中文-', - 'Xmp.MicrosoftPhoto.LastKeywordXMP' : ['test-中文-'], - 'Xmp.xmpMM.InstanceID' : 'xmp.iid:14282d1f-7831-7043-ad77-1e10959ecd50', - 'Xmp.xmpMM.DocumentID' : 'ECE1099AF3406874FAA7B01CBB5C6F71', - 'Xmp.xmpMM.OriginalDocumentID' : 'ECE1099AF3406874FAA7B01CBB5C6F71', - 'Xmp.xmpMM.History' : [''], - 'Xmp.xmpMM.History[1]' : 'type="Struct"', - 'Xmp.xmpMM.History[1]/stEvt:action' : 'saved', - 'Xmp.xmpMM.History[1]/stEvt:instanceID' : 'xmp.iid:8f83ee32-1163-7b40-b31b-deab20789cf4', - 'Xmp.xmpMM.History[1]/stEvt:when' : '2019-08-19T19:45:55+08:00', - 'Xmp.xmpMM.History[1]/stEvt:softwareAgent' : 'Adobe Photoshop Camera Raw 10.0', - 'Xmp.xmpMM.History[1]/stEvt:changed' : '/metadata', - 'Xmp.xmpMM.History[2]' : 'type="Struct"', - 'Xmp.xmpMM.History[2]/stEvt:action' : 'saved', - 'Xmp.xmpMM.History[2]/stEvt:instanceID' : 'xmp.iid:14282d1f-7831-7043-ad77-1e10959ecd50', - 'Xmp.xmpMM.History[2]/stEvt:when' : '2020-04-06T00:55:07+08:00', - 'Xmp.xmpMM.History[2]/stEvt:softwareAgent' : 'Adobe Photoshop Camera Raw (Windows)', - 'Xmp.xmpMM.History[2]/stEvt:changed' : '/metadata', - 'Xmp.photoshop.DateCreated' : '2019-08-12T19:44:04Z', - 'Xmp.iptc.CreatorContactInfo' : 'type="Struct"', - 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiTelWork' : '123456', - 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiEmailWork': '123456@gmail.com', - 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiUrlWork' : 'www.123456.com', +IPTC = {tag:tag_detail['value'] for tag,tag_detail in IPTC_DETAIL.items()} + +XMP_DETAIL = { + 'Xmp.dc.format': { + 'value': 'image/jpeg', + 'typeName': 'XmpText', + 'tagDesc': 'The file format used when saving the resource. Tools and applications should set this property to the save format of the data. It may include appropriate qualifiers.', + 'tagLabel': 'Format' + }, + 'Xmp.dc.subject': { + 'value': ['tag1', 'tag2', 'tag3'], + 'typeName': 'XmpBag', + 'tagDesc': 'An unordered array of descriptive phrases or keywords that specify the topic of the content of the resource.', + 'tagLabel': 'Subject' + }, + 'Xmp.dc.creator': { + 'value': ['test-中文-'], + 'typeName': 'XmpSeq', + 'tagDesc': 'The authors of the resource (listed in order of precedence, if significant).', + 'tagLabel': 'Creator' + }, + 'Xmp.dc.title': { + 'value': { + 'lang="x-default"': 'test-中文-', + 'lang="de-DE"': 'Hallo, Welt' + }, + 'typeName': 'LangAlt', + 'tagDesc': 'The title of the document, or the name given to the resource. Typically, it will be a name by which the resource is formally known.', + 'tagLabel': 'Title' + }, + 'Xmp.dc.rights': { + 'value': { + 'lang="x-default"': 'test-中文-' + }, + 'typeName': 'LangAlt', + 'tagDesc': 'Informal rights statement, selected by language. Typically, rights information includes a statement about various property rights associated with the resource, including intellectual property rights.', + 'tagLabel': 'Rights' + }, + 'Xmp.dc.description': { + 'value': { + 'lang="x-default"': 'test-中文-' + }, + 'typeName': 'LangAlt', + 'tagDesc': 'A textual description of the content of the resource. Multiple values may be present for different languages.', + 'tagLabel': 'Description' + }, + 'Xmp.xmp.Rating': { + 'value': '4', + 'typeName': 'XmpText', + 'tagDesc': "A number that indicates a document's status relative to other documents, used to organize documents in a file browser. Values are user-defined within an application-defined range.", + 'tagLabel': 'Rating' + }, + 'Xmp.xmp.CreateDate': { + 'value': '2019-08-12T19:44:04.176', + 'typeName': 'XmpText', + 'tagDesc': 'The date and time the resource was originally created.', + 'tagLabel': 'Create Date' + }, + 'Xmp.xmp.ModifyDate': { + 'value': '2019-08-12T19:44:04.18', + 'typeName': 'XmpText', + 'tagDesc': "The date and time the resource was last modified. Note: The value of this property is not necessarily the same as the file's system modification date because it is set before the file is saved.", + 'tagLabel': 'Modify Date' + }, + 'Xmp.xmp.MetadataDate': { + 'value': '2020-04-06T00:55:07+08:00', + 'typeName': 'XmpText', + 'tagDesc': 'The date and time that any metadata for this resource was last changed. It should be the same as or more recent than xmp:ModifyDate.', + 'tagLabel': 'Metadata Date' + }, + 'Xmp.MicrosoftPhoto.Rating': { + 'value': '75', + 'typeName': 'XmpText', + 'tagDesc': 'Rating Percent.', + 'tagLabel': 'Rating Percent' + }, + 'Xmp.MicrosoftPhoto.DateAcquired': { + 'value': '2019-08-12T19:44:08.151', + 'typeName': 'XmpText', + 'tagDesc': 'Date Acquired.', + 'tagLabel': 'Date Acquired' + }, + 'Xmp.MicrosoftPhoto.LensModel': { + 'value': 'test-中文-', + 'typeName': 'XmpText', + 'tagDesc': 'Lens Model.', + 'tagLabel': 'Lens Model' + }, + 'Xmp.MicrosoftPhoto.LensManufacturer': { + 'value': 'test-中文-', + 'typeName': 'XmpText', + 'tagDesc': 'Lens Manufacturer.', + 'tagLabel': 'Lens Manufacturer' + }, + 'Xmp.MicrosoftPhoto.FlashModel': { + 'value': 'test-中文-', + 'typeName': 'XmpText', + 'tagDesc': 'Flash Model.', + 'tagLabel': 'Flash Model' + }, + 'Xmp.MicrosoftPhoto.FlashManufacturer': { + 'value': 'test-中文-', + 'typeName': 'XmpText', + 'tagDesc': 'Flash Manufacturer.', + 'tagLabel': 'Flash Manufacturer' + }, + 'Xmp.MicrosoftPhoto.CameraSerialNumber': { + 'value': 'test-中文-', + 'typeName': 'XmpText', + 'tagDesc': 'Camera Serial Number.', + 'tagLabel': 'Camera Serial Number' + }, + 'Xmp.MicrosoftPhoto.LastKeywordXMP': { + 'value': ['test-中文-'], + 'typeName': 'XmpBag', + 'tagDesc': 'Last Keyword XMP.', + 'tagLabel': 'Last Keyword XMP' + }, + 'Xmp.xmpMM.InstanceID': { + 'value': 'xmp.iid:14282d1f-7831-7043-ad77-1e10959ecd50', + 'typeName': 'XmpText', + 'tagDesc': 'An identifier for a specific incarnation of a document, updated each time a file is saved. It should be based on a UUID; see Document and Instance IDs below.', + 'tagLabel': 'Instance ID' + }, + 'Xmp.xmpMM.DocumentID': { + 'value': 'ECE1099AF3406874FAA7B01CBB5C6F71', + 'typeName': 'XmpText', + 'tagDesc': 'The common identifier for all versions and renditions of a document. It should be based on a UUID; see Document and Instance IDs below.', + 'tagLabel': 'Document ID' + }, + 'Xmp.xmpMM.OriginalDocumentID': { + 'value': 'ECE1099AF3406874FAA7B01CBB5C6F71', + 'typeName': 'XmpText', + 'tagDesc': 'Refer to Part 1, Data Model, Serialization, and Core Properties, for definition.', + 'tagLabel': 'Original Document ID' + }, + 'Xmp.xmpMM.History': { + 'value': [''], + 'typeName': 'XmpText', + 'tagDesc': 'An ordered array of high-level user actions that resulted in this resource. It is intended to give human readers a general indication of the steps taken to make the changes from the previous version to this one. The list should be at an abstract level; it is not intended to be an exhaustive keystroke or other detailed history.', + 'tagLabel': 'History' + }, + 'Xmp.xmpMM.History[1]': { + 'value': 'type="Struct"', + 'typeName': 'XmpText', + 'tagDesc': '', + 'tagLabel': 'History[1]' + }, + 'Xmp.xmpMM.History[1]/stEvt:action': { + 'value': 'saved', + 'typeName': 'XmpText', + 'tagDesc': '', + 'tagLabel': 'History[1]/stEvt:action' + }, + 'Xmp.xmpMM.History[1]/stEvt:instanceID': { + 'value': 'xmp.iid:8f83ee32-1163-7b40-b31b-deab20789cf4', + 'typeName': 'XmpText', + 'tagDesc': '', + 'tagLabel': 'History[1]/stEvt:instanceID' + }, + 'Xmp.xmpMM.History[1]/stEvt:when': { + 'value': '2019-08-19T19:45:55+08:00', + 'typeName': 'XmpText', + 'tagDesc': '', + 'tagLabel': 'History[1]/stEvt:when' + }, + 'Xmp.xmpMM.History[1]/stEvt:softwareAgent': { + 'value': 'Adobe Photoshop Camera Raw 10.0', + 'typeName': 'XmpText', + 'tagDesc': '', + 'tagLabel': 'History[1]/stEvt:softwareAgent' + }, + 'Xmp.xmpMM.History[1]/stEvt:changed': { + 'value': '/metadata', + 'typeName': 'XmpText', + 'tagDesc': '', + 'tagLabel': 'History[1]/stEvt:changed' + }, + 'Xmp.xmpMM.History[2]': { + 'value': 'type="Struct"', + 'typeName': 'XmpText', + 'tagDesc': '', + 'tagLabel': 'History[2]' + }, + 'Xmp.xmpMM.History[2]/stEvt:action': { + 'value': 'saved', + 'typeName': 'XmpText', + 'tagDesc': '', + 'tagLabel': 'History[2]/stEvt:action' + }, + 'Xmp.xmpMM.History[2]/stEvt:instanceID': { + 'value': 'xmp.iid:14282d1f-7831-7043-ad77-1e10959ecd50', + 'typeName': 'XmpText', + 'tagDesc': '', + 'tagLabel': 'History[2]/stEvt:instanceID' + }, + 'Xmp.xmpMM.History[2]/stEvt:when': { + 'value': '2020-04-06T00:55:07+08:00', + 'typeName': 'XmpText', + 'tagDesc': '', + 'tagLabel': 'History[2]/stEvt:when' + }, + 'Xmp.xmpMM.History[2]/stEvt:softwareAgent': { + 'value': 'Adobe Photoshop Camera Raw (Windows)', + 'typeName': 'XmpText', + 'tagDesc': '', + 'tagLabel': 'History[2]/stEvt:softwareAgent' + }, + 'Xmp.xmpMM.History[2]/stEvt:changed': { + 'value': '/metadata', + 'typeName': 'XmpText', + 'tagDesc': '', + 'tagLabel': 'History[2]/stEvt:changed' + }, + 'Xmp.photoshop.DateCreated': { + 'value': '2019-08-12T19:44:04Z', + 'typeName': 'XmpText', + 'tagDesc': 'The date the intellectual content of the document was created (rather than the creation date of the physical representation), following IIM conventions. For example, a photo taken during the American Civil War would have a creation date during that epoch (1861-1865) rather than the date the photo was digitized for archiving.', + 'tagLabel': 'Date Created' + }, + 'Xmp.iptc.CreatorContactInfo': { + 'value': 'type="Struct"', + 'typeName': 'XmpText', + 'tagDesc': "The creator's contact information provides all necessary information to get in contact with the creator of this image and comprises a set of sub-properties for proper addressing.", + 'tagLabel': "Creator's Contact Info" + }, + 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiTelWork': { + 'value': '123456', + 'typeName': 'XmpText', + 'tagDesc': 'Deprecated, use the CiTelWork tag in ContactInfo struct instead. sub-key Creator Contact Info: phone number.', + 'tagLabel': 'Contact Info-Phone (deprecated)' + }, + 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiEmailWork': { + 'value': '123456@gmail.com', + 'typeName': 'XmpText', + 'tagDesc': 'Deprecated, use the CiEmailWork tag in ContactInfo struct instead. sub-key Creator Contact Info: email address.', + 'tagLabel': 'Contact Info-Email (deprecated)' + }, + 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiUrlWork': { + 'value': 'www.123456.com', + 'typeName': 'XmpText', + 'tagDesc': 'Deprecated, use the CiUrlWork tag in ContactInfo struct instead. sub-key Creator Contact Info: web address.', + 'tagLabel': 'Contact Info-Web URL (deprecated)' } +} + +XMP = {tag:tag_detail['value'] for tag,tag_detail in XMP_DETAIL.items()} RAW_XMP = """ tag1 tag2 tag3 test-中文- test-中文-, lang="de-DE" Hallo, Welt test-中文- test-中文- test-中文- """.strip('\n') - -COMMENT = 'Hello World! \n你好!\n' - -current_dir = os.path.dirname(__file__) - -with open(os.path.join(current_dir, 'gray.icc'), 'rb') as f: - GRAY_ICC = f.read() - -with open(os.path.join(current_dir, 'rgb.icc'), 'rb') as f: - RGB_ICC = f.read() - -with open(os.path.join(current_dir, '1-thumb.jpg'), 'rb') as f: - EXIF_THUMB = f.read() diff --git a/pyexiv2/tests/test_func.py b/pyexiv2/tests/test_func.py index 60a7bd6..132e4c3 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.28.1' + assert __exiv2_version__ == '0.28.3' except: ENV.skip_test = True raise @@ -64,16 +64,31 @@ def test_read_exif(): check_img_md5() +def test_read_exif_detail(): + diff_dict(data.EXIF_DETAIL, ENV.img.read_exif_detail()) + check_img_md5() + + def test_read_iptc(): diff_dict(data.IPTC, ENV.img.read_iptc()) check_img_md5() +def test_read_iptc_detail(): + diff_dict(data.IPTC_DETAIL, ENV.img.read_iptc_detail()) + check_img_md5() + + def test_read_xmp(): diff_dict(data.XMP, ENV.img.read_xmp()) check_img_md5() +def test_read_xmp_detail(): + diff_dict(data.XMP_DETAIL, ENV.img.read_xmp_detail()) + check_img_md5() + + def test_read_raw_xmp(): diff_text(data.RAW_XMP, ENV.img.read_raw_xmp()) check_img_md5() @@ -250,19 +265,9 @@ def test_registerNs(): def test_enableBMFF(): - with pytest.raises(RuntimeError): - with Image(ENV.heic_img) as img: - pass - - assert enableBMFF() == True with Image(ENV.heic_img) as img: assert img.read_exif() - assert enableBMFF(False) == True - with pytest.raises(RuntimeError): - with Image(ENV.heic_img) as img: - pass - def test_log_level(): with pytest.raises(RuntimeError): diff --git a/setup.py b/setup.py index 17acd54..efe1ad3 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setuptools.setup( name='pyexiv2', - version='2.12.0', # need to set the variable in 'pyexiv2/__init__.py' + version='2.14.0', # need to set the variable in 'pyexiv2/__init__.py' author='LeoHsiao', author_email='leohsiao@foxmail.com', description='Read and write image metadata, including EXIF, IPTC, XMP, ICC Profile.',