Skip to content

Commit 5c3bbbf

Browse files
committed
Merge the changes to support ExifTool.set_json_loads() for final 0.5.6 release
2 parents 7c59336 + d510a94 commit 5c3bbbf

25 files changed

+698
-84
lines changed

.github/workflows/lint-and-test.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ jobs:
6868
echo "PATH=`pwd`:$PATH" >> $GITHUB_ENV
6969
- name: Install pyexiftool
7070
run: |
71-
python -m pip install .
71+
# install all supported json processors for tests
72+
python -m pip install .[json,test]
7273
- name: Lint with flake8
7374
run: |
7475
# stop the build if there are Python syntax errors or undefined names

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Date (Timezone) | Version | Comment
99
03/26/2022 06:48:01 AM (PDT) | 0.5.3 | Quite a few docstring changes<br>ExifToolHelper's get_tags() and set_tags() checks tag names to prevent inadvertent write behavior<br>Renamed a few of the errors to make sure the errors are explicit<br>ExifToolHelper() has some static helper methods which can be used when extending the class (ExifToolAlpha.set_keywords_batch() demonstrates a sample usage).<br>setup.py tweaked to make it Beta rather than Alpha<br>ExifToolAlpha.get_tag() updated to make it more robust.<br>Fixed ujson compatibility<br>Cleaned up and refactored testing.
1010
08/27/2022 06:06:32 PM (PDT) | 0.5.4 | New Feature: added raw_bytes parameter to ExifTool.execute() to return bytes only with no decoding conversion.<br>Changed: ExifTool.execute() now accepts both [str,bytes]. When given str, it will encode according to the ExifTool.encoding property.<br>Changed: ExifToolHelper.execute() now accepts Any type, and will do a str() on any non-str parameter.<br>Technical change: Popen() no longer uses an -encoding parameter, therefore working with the socket is back to bytes when interfacing with the exiftool subprocess. This should be invisible to most users as the default behavior will still be the same.<br>Tests: Created associated test with a custom makernotes example to write and read back bytes.<br>Docs: Updated documentation with comprehensive samples, and a better FAQ section for common problems.
1111
12/30/2022 02:35:18 PM (PST) | 0.5.5 | No functional changes, only a huge speed improvement with large operations :: Update: Speed up large responses from exiftool. Instead of using + string concatenation, uses list appends and reverse(), which results in a speedup of 10x+ for large operations. See more details from the [reported issue](https://github.com/sylikc/pyexiftool/issues/60) and [PR 61](https://github.com/sylikc/pyexiftool/pull/61) by [prutschman](https://github.com/prutschman)
12+
10/22/2023 03:21:46 PM (PDT) | 0.5.6 | New Feature: added method ExifTool.set_json_loads() which allows setting a method to replace the json.loads() called in ExifTool.execute_json().<br>This permits passing additional configuration parameters to address the [reported issue](https://github.com/sylikc/pyexiftool/issues/76).<br>All documentation has been updated and two accompanying FAQ entries have been written to describe the new functionality. Test cases have been written to test the new functionality and some baseline exiftool tests to ensure that the behavior remains consistent across tests.
1213

1314

1415
Follow maintenance/release-process.html when releasing a version.
@@ -61,9 +62,10 @@ Check for changes at the following resources to see if anyone has added some nif
6162

6263
We can also direct users here or answer existing questions as to how to use the original version of ExifTool.
6364

64-
(last checked 2/28/2022 all)
65+
(last checked 10/23/2023 all)
6566

6667
search "pyexiftool github" to see if you find any more random ports/forks
6768
check for updates https://github.com/smarnach/pyexiftool/pulls
6869
check for new open issues https://github.com/smarnach/pyexiftool/issues?q=is%3Aissue+is%3Aopen
6970

71+
answer relevant issues on stackoverflow (make sure it's related to the latest version) https://stackoverflow.com/search?tab=newest&q=pyexiftool&searchOn=3

COPYING.BSD

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright 2012 Sven Marnach, 2019-2022 Kevin M (sylikc)
1+
Copyright 2012 Sven Marnach, 2019-2023 Kevin M (sylikc)
22
All rights reserved.
33

44
Redistribution and use in source and binary forms, with or without

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
PyExifTool <http://github.com/sylikc/pyexiftool>
22

3-
Copyright 2019-2022 Kevin M (sylikc)
3+
Copyright 2019-2023 Kevin M (sylikc)
44
Copyright 2012-2014 Sven Marnach
55

66
PyExifTool is free software: you can redistribute it and/or modify

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
# General information about the project.
3333
project = 'PyExifTool'
34-
copyright = '2022, Kevin M (sylikc)'
34+
copyright = '2023, Kevin M (sylikc)'
3535
author = 'Kevin M (sylikc)'
3636

3737
# read directly from exiftool's version instead of hard coding it here

docs/source/faq.rst

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,97 @@ So if you want to have the ouput match (*useful for debugging*) between PyExifTo
103103
Y Resolution : 72
104104
105105
106+
.. _set_json_loads faq:
107+
108+
PyExifTool json turns some text fields into numbers
109+
===================================================
110+
111+
A strange behavior of *exiftool* is documented in the `exiftool documentation`_::
112+
113+
-j[[+]=JSONFILE] (-json)
114+
115+
Note that ExifTool quotes JSON values only if they don't look like numbers
116+
(regardless of the original storage format or the relevant metadata specification).
117+
118+
.. _`exiftool documentation`: https://exiftool.org/exiftool_pod.html#OPTIONS
119+
120+
This causes a peculiar behavior if you set a text metadata field to a string that looks like a number:
121+
122+
.. code-block::
123+
124+
import exiftool
125+
with exiftool.ExifToolHelper() as et:
126+
# Comment is a STRING field
127+
et.set_tags("rose.jpg", {"Comment": "1.10"}) # string: "1.10" != "1.1"
128+
129+
# FocalLength is a FLOAT field
130+
et.set_tags("rose.jpg", {"FocalLength": 1.10}) # float: 1.10 == 1.1
131+
print(et.get_tags("rose.jpg", ["Comment", "FocalLength"]))
132+
133+
# Prints: [{'SourceFile': 'rose.jpg', 'File:Comment': 1.1, 'EXIF:FocalLength': 1.1}]
134+
135+
Workaround to enable output as string
136+
-------------------------------------
137+
138+
There is no universal fix which wouldn't affect other behaviors in PyExifTool, so this is an advanced workaround if you encounter this specific problem.
139+
140+
PyExifTool does not do any processing on the fields returned by *exiftool*. In effect, what is returned is processed directly by ``json.loads()`` by default.
141+
142+
You can change the behavior of the json string parser, or specify a different one using :py:meth:`exiftool.ExifTool.set_json_loads`.
143+
144+
The `documentation of CPython's json.load`_ allows ``parse_float`` to be any parser of choice when a float is encountered in a JSON file. Thus, you can force the float to be interpreted as a string.
145+
However, as you can see below, it also *changes the behavior of all float fields*.
146+
147+
148+
.. _`documentation of CPython's json.load`: https://docs.python.org/3/library/json.html#json.load
149+
150+
.. code-block::
151+
152+
import exiftool, json
153+
with exiftool.ExifToolHelper() as et:
154+
et.set_json_loads(json.loads, parse_float=str)
155+
156+
# Comment is a STRING field
157+
et.set_tags("rose.jpg", {"Comment": "1.10"}) # string: "1.10" == "1.10"
158+
159+
# FocalLength is a FLOAT field
160+
et.set_tags("rose.jpg", {"FocalLength": 1.10}) # float: 1.1 == "1.1"
161+
print(et.get_tags("rose.jpg", ["Comment", "FocalLength"]))
162+
163+
# Prints: [{'SourceFile': 'rose.jpg', 'File:Comment': '1.10', 'EXIF:FocalLength': '1.1'}]
164+
165+
.. warning::
166+
167+
Unfortunately you can either change all float fields to a string, or possibly lose some float precision when working with floats in string metadata fields.
168+
169+
There isn't any known universal workaround which wouldn't break one thing or the other, as it is an underlying *exiftool* quirk.
170+
171+
There are other edge cases which may exhibit quirky behavior when storing numbers and whitespace only to text fields (See `test cases related to numeric tags`_). Since PyExifTool cannot accommodate all possible edge cases,
172+
this workaround will allow you to configure PyExifTool to work in your environment!
173+
174+
.. _`test cases related to numeric tags`: https://github.com/sylikc/pyexiftool/blob/master/tests/test_helper_tags_float.py
175+
176+
177+
I would like to use a faster json string parser
178+
===============================================
179+
180+
By default, PyExifTool uses the built-in ``json`` library to load the json string returned by *exiftool*. If you would like to use an alternate library, set it manually using :py:meth:`exiftool.ExifTool.set_json_loads`
181+
182+
183+
.. code-block::
184+
185+
import exiftool, json
186+
with exiftool.ExifToolHelper() as et:
187+
et.set_json_loads(ujson.loads)
188+
...
189+
190+
.. note::
191+
192+
In PyExifTool version before 0.5.6, ``ujson`` was supported automatically if the package was installed.
193+
194+
To support any possible alternative JSON library, this behavior has now been changed and it must be enabled manually.
195+
196+
106197
I'm getting an error! How do I debug PyExifTool output?
107198
=======================================================
108199

docs/source/maintenance/release-process.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Source Preparation
99
==================
1010

1111
#. Update the version number in ``exiftool/__init__.py``
12+
#. Update the docs copyright year ``docs/source/conf.py`` and in source files
1213
#. Add any changelog entries to ``CHANGELOG.md``
1314
#. Run Tests
1415
#. Generate docs

exiftool/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#
55
# PyExifTool <http://github.com/sylikc/pyexiftool>
66
#
7-
# Copyright 2019-2022 Kevin M (sylikc)
7+
# Copyright 2019-2023 Kevin M (sylikc)
88
# Copyright 2012-2014 Sven Marnach
99
#
1010
# Community contributors are listed in the CHANGELOG.md for the PRs
@@ -61,7 +61,7 @@
6161

6262
# version number using Semantic Versioning 2.0.0 https://semver.org/
6363
# may not be PEP-440 compliant https://www.python.org/dev/peps/pep-0440/#semantic-versioning
64-
__version__ = "0.5.5"
64+
__version__ = "0.5.6"
6565

6666

6767
# while we COULD import all the exceptions into the base library namespace,

exiftool/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#
55
# PyExifTool <http://github.com/sylikc/pyexiftool>
66
#
7-
# Copyright 2019-2022 Kevin M (sylikc)
7+
# Copyright 2019-2023 Kevin M (sylikc)
88
# Copyright 2012-2014 Sven Marnach
99
#
1010
# Community contributors are listed in the CHANGELOG.md for the PRs

exiftool/exceptions.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#
55
# PyExifTool <http://github.com/sylikc/pyexiftool>
66
#
7-
# Copyright 2019-2022 Kevin M (sylikc)
7+
# Copyright 2019-2023 Kevin M (sylikc)
88
# Copyright 2012-2014 Sven Marnach
99
#
1010
# Community contributors are listed in the CHANGELOG.md for the PRs
@@ -53,19 +53,18 @@ class ExifToolRunning(ExifToolProcessStateError):
5353
"""
5454
ExifTool is already running
5555
"""
56-
def __init__(self, message):
56+
def __init__(self, message: str):
5757
super().__init__(f"ExifTool instance is running: {message}")
5858

5959

6060
class ExifToolNotRunning(ExifToolProcessStateError):
6161
"""
6262
ExifTool is not running
6363
"""
64-
def __init__(self, message):
64+
def __init__(self, message: str):
6565
super().__init__(f"ExifTool instance not running: {message}")
6666

6767

68-
6968
###########################################################
7069
#################### Execute Exception ####################
7170
###########################################################
@@ -94,7 +93,6 @@ def __init__(self, message, exit_status, cmd_stdout, cmd_stderr, params):
9493
self.stderr: str = cmd_stderr
9594

9695

97-
9896
class ExifToolExecuteError(ExifToolExecuteException):
9997
"""
10098
ExifTool executed the command but returned a non-zero exit status.
@@ -139,7 +137,6 @@ def __init__(self, exit_status, cmd_stdout, cmd_stderr, params):
139137
super().__init__("execute_json received invalid JSON output from exiftool", exit_status, cmd_stdout, cmd_stderr, params)
140138

141139

142-
143140
#########################################################
144141
#################### Other Exception ####################
145142
#########################################################
@@ -159,4 +156,3 @@ class ExifToolTagNameError(ExifToolException):
159156
"""
160157
def __init__(self, bad_tag):
161158
super().__init__(f"Invalid Tag Name found: \"{bad_tag}\"")
162-

0 commit comments

Comments
 (0)