Removed debug code from exiftool, fixed #641
This commit is contained in:
@@ -1741,7 +1741,7 @@ Substitution Description
|
|||||||
{lf} A line feed: '\n', alias for {newline}
|
{lf} A line feed: '\n', alias for {newline}
|
||||||
{cr} A carriage return: '\r'
|
{cr} A carriage return: '\r'
|
||||||
{crlf} a carriage return + line feed: '\r\n'
|
{crlf} a carriage return + line feed: '\r\n'
|
||||||
{osxphotos_version} The osxphotos version, e.g. '0.46.2'
|
{osxphotos_version} The osxphotos version, e.g. '0.46.4'
|
||||||
{osxphotos_cmd_line} The full command line used to run osxphotos
|
{osxphotos_cmd_line} The full command line used to run osxphotos
|
||||||
|
|
||||||
The following substitutions may result in multiple values. Thus if specified for
|
The following substitutions may result in multiple values. Thus if specified for
|
||||||
@@ -3645,7 +3645,7 @@ The following template field substitutions are availabe for use the templating s
|
|||||||
|{lf}|A line feed: '\n', alias for {newline}|
|
|{lf}|A line feed: '\n', alias for {newline}|
|
||||||
|{cr}|A carriage return: '\r'|
|
|{cr}|A carriage return: '\r'|
|
||||||
|{crlf}|a carriage return + line feed: '\r\n'|
|
|{crlf}|a carriage return + line feed: '\r\n'|
|
||||||
|{osxphotos_version}|The osxphotos version, e.g. '0.46.2'|
|
|{osxphotos_version}|The osxphotos version, e.g. '0.46.4'|
|
||||||
|{osxphotos_cmd_line}|The full command line used to run osxphotos|
|
|{osxphotos_cmd_line}|The full command line used to run osxphotos|
|
||||||
|{album}|Album(s) photo is contained in|
|
|{album}|Album(s) photo is contained in|
|
||||||
|{folder_album}|Folder path + album photo is contained in. e.g. 'Folder/Subfolder/Album' or just 'Album' if no enclosing folder|
|
|{folder_album}|Folder path + album photo is contained in. e.g. 'Folder/Subfolder/Album' or just 'Album' if no enclosing folder|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Sphinx build info version 1
|
# Sphinx build info version 1
|
||||||
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
|
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
|
||||||
config: 3bdc7daae06c46fa0e6357e497a27088
|
config: 4fd4a10e261cc9bab3c5f7edf97d5f38
|
||||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Overview: module code — osxphotos 0.46.2 documentation</title>
|
<title>Overview: module code — osxphotos 0.46.4 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
|
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="../_static/alabaster.css" />
|
<link rel="stylesheet" type="text/css" href="../_static/alabaster.css" />
|
||||||
<script data-url_root="../" id="documentation_options" src="../_static/documentation_options.js"></script>
|
<script data-url_root="../" id="documentation_options" src="../_static/documentation_options.js"></script>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>osxphotos.photoinfo — osxphotos 0.46.1 documentation</title>
|
<title>osxphotos.photoinfo — osxphotos 0.46.4 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
|
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="../../_static/alabaster.css" />
|
<link rel="stylesheet" type="text/css" href="../../_static/alabaster.css" />
|
||||||
<script data-url_root="../../" id="documentation_options" src="../../_static/documentation_options.js"></script>
|
<script data-url_root="../../" id="documentation_options" src="../../_static/documentation_options.js"></script>
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
<span class="kn">from</span> <span class="nn">.searchinfo</span> <span class="kn">import</span> <span class="n">SearchInfo</span>
|
<span class="kn">from</span> <span class="nn">.searchinfo</span> <span class="kn">import</span> <span class="n">SearchInfo</span>
|
||||||
<span class="kn">from</span> <span class="nn">.text_detection</span> <span class="kn">import</span> <span class="n">detect_text</span>
|
<span class="kn">from</span> <span class="nn">.text_detection</span> <span class="kn">import</span> <span class="n">detect_text</span>
|
||||||
<span class="kn">from</span> <span class="nn">.uti</span> <span class="kn">import</span> <span class="n">get_preferred_uti_extension</span><span class="p">,</span> <span class="n">get_uti_for_extension</span>
|
<span class="kn">from</span> <span class="nn">.uti</span> <span class="kn">import</span> <span class="n">get_preferred_uti_extension</span><span class="p">,</span> <span class="n">get_uti_for_extension</span>
|
||||||
<span class="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="n">_debug</span><span class="p">,</span> <span class="n">_get_resource_loc</span><span class="p">,</span> <span class="n">list_directory</span>
|
<span class="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="n">_debug</span><span class="p">,</span> <span class="n">_get_resource_loc</span><span class="p">,</span> <span class="n">list_directory</span><span class="p">,</span> <span class="n">_debug</span>
|
||||||
|
|
||||||
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"PhotoInfo"</span><span class="p">,</span> <span class="s2">"PhotoInfoNone"</span><span class="p">]</span>
|
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"PhotoInfo"</span><span class="p">,</span> <span class="s2">"PhotoInfoNone"</span><span class="p">]</span>
|
||||||
|
|
||||||
@@ -621,7 +621,7 @@
|
|||||||
<span class="nd">@property</span>
|
<span class="nd">@property</span>
|
||||||
<span class="k">def</span> <span class="nf">ismissing</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
<span class="k">def</span> <span class="nf">ismissing</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||||
<span class="sd">"""returns true if photo is missing from disk (which means it's not been downloaded from iCloud)</span>
|
<span class="sd">"""returns true if photo is missing from disk (which means it's not been downloaded from iCloud)</span>
|
||||||
<span class="sd"> </span>
|
|
||||||
<span class="sd"> NOTE: the photos.db database uses an asynchrounous write-ahead log so changes in Photos</span>
|
<span class="sd"> NOTE: the photos.db database uses an asynchrounous write-ahead log so changes in Photos</span>
|
||||||
<span class="sd"> do not immediately get written to disk. In particular, I've noticed that downloading</span>
|
<span class="sd"> do not immediately get written to disk. In particular, I've noticed that downloading</span>
|
||||||
<span class="sd"> an image from the cloud does not force the database to be updated until something else</span>
|
<span class="sd"> an image from the cloud does not force the database to be updated until something else</span>
|
||||||
|
|||||||
2
docs/_static/documentation_options.js
vendored
2
docs/_static/documentation_options.js
vendored
@@ -1,6 +1,6 @@
|
|||||||
var DOCUMENTATION_OPTIONS = {
|
var DOCUMENTATION_OPTIONS = {
|
||||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
||||||
VERSION: '0.46.2',
|
VERSION: '0.46.4',
|
||||||
LANGUAGE: 'None',
|
LANGUAGE: 'None',
|
||||||
COLLAPSE_INDEX: false,
|
COLLAPSE_INDEX: false,
|
||||||
BUILDER: 'html',
|
BUILDER: 'html',
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||||
|
|
||||||
<title>osxphotos command line interface (CLI) — osxphotos 0.46.2 documentation</title>
|
<title>osxphotos command line interface (CLI) — osxphotos 0.46.4 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
||||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Index — osxphotos 0.46.2 documentation</title>
|
<title>Index — osxphotos 0.46.4 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
||||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||||
|
|
||||||
<title>Welcome to osxphotos’s documentation! — osxphotos 0.46.2 documentation</title>
|
<title>Welcome to osxphotos’s documentation! — osxphotos 0.46.4 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
||||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||||
|
|
||||||
<title>osxphotos — osxphotos 0.46.2 documentation</title>
|
<title>osxphotos — osxphotos 0.46.4 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
||||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||||
|
|
||||||
<title>osxphotos package — osxphotos 0.46.2 documentation</title>
|
<title>osxphotos package — osxphotos 0.46.4 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
||||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Search — osxphotos 0.46.2 documentation</title>
|
<title>Search — osxphotos 0.46.4 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
""" version info """
|
""" version info """
|
||||||
|
|
||||||
__version__ = "0.46.3"
|
__version__ = "0.46.4"
|
||||||
|
|||||||
@@ -105,10 +105,9 @@ class _ExifToolProc:
|
|||||||
|
|
||||||
return cls.instance
|
return cls.instance
|
||||||
|
|
||||||
def __init__(self, exiftool=None, debug=False):
|
def __init__(self, exiftool=None):
|
||||||
"""construct _ExifToolProc singleton object or return instance of already created object
|
"""construct _ExifToolProc singleton object or return instance of already created object
|
||||||
exiftool: optional path to exiftool binary (if not provided, will search path to find it)
|
exiftool: optional path to exiftool binary (if not provided, will search path to find it)
|
||||||
debug: optional bool to enable debugging output
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if hasattr(self, "_process_running") and self._process_running:
|
if hasattr(self, "_process_running") and self._process_running:
|
||||||
@@ -119,11 +118,8 @@ class _ExifToolProc:
|
|||||||
f"ignoring exiftool={exiftool}"
|
f"ignoring exiftool={exiftool}"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
self.debug = debug
|
|
||||||
self._process_running = False
|
self._process_running = False
|
||||||
self._exiftool = exiftool or get_exiftool_path()
|
self._exiftool = exiftool or get_exiftool_path()
|
||||||
if self.debug:
|
|
||||||
logging.debug(f"exiftool={self._exiftool}")
|
|
||||||
self._start_proc()
|
self._start_proc()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -177,17 +173,10 @@ class _ExifToolProc:
|
|||||||
self._process_running = True
|
self._process_running = True
|
||||||
|
|
||||||
EXIFTOOL_PROCESSES.append(self)
|
EXIFTOOL_PROCESSES.append(self)
|
||||||
if self.debug:
|
|
||||||
logging.debug(
|
|
||||||
"exiftool process started: {self._process} {self._process_running}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _stop_proc(self):
|
def _stop_proc(self):
|
||||||
"""stop the exiftool process if it's running, otherwise, do nothing"""
|
"""stop the exiftool process if it's running, otherwise, do nothing"""
|
||||||
|
|
||||||
if self.debug:
|
|
||||||
logging.debug(f"exiftool process stopping: {self._process}")
|
|
||||||
|
|
||||||
if not self._process_running:
|
if not self._process_running:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -207,16 +196,11 @@ class _ExifToolProc:
|
|||||||
del self._process
|
del self._process
|
||||||
self._process_running = False
|
self._process_running = False
|
||||||
|
|
||||||
if self.debug:
|
|
||||||
logging.debug(f"exiftool process stopped: {self._process}")
|
|
||||||
|
|
||||||
|
|
||||||
class ExifTool:
|
class ExifTool:
|
||||||
"""Basic exiftool interface for reading and writing EXIF tags"""
|
"""Basic exiftool interface for reading and writing EXIF tags"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, filepath, exiftool=None, overwrite=True, flags=None):
|
||||||
self, filepath, exiftool=None, overwrite=True, flags=None, debug=False
|
|
||||||
):
|
|
||||||
"""Create ExifTool object
|
"""Create ExifTool object
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -224,7 +208,6 @@ class ExifTool:
|
|||||||
exiftool: path to exiftool, if not specified will look in path
|
exiftool: path to exiftool, if not specified will look in path
|
||||||
overwrite: if True, will overwrite image file without creating backup, default=False
|
overwrite: if True, will overwrite image file without creating backup, default=False
|
||||||
flags: optional list of exiftool flags to prepend to exiftool command when writing metadata (e.g. -m or -F)
|
flags: optional list of exiftool flags to prepend to exiftool command when writing metadata (e.g. -m or -F)
|
||||||
debug: if True, enables debug output
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
ExifTool instance
|
ExifTool instance
|
||||||
@@ -232,13 +215,12 @@ class ExifTool:
|
|||||||
self.file = filepath
|
self.file = filepath
|
||||||
self.overwrite = overwrite
|
self.overwrite = overwrite
|
||||||
self.flags = flags or []
|
self.flags = flags or []
|
||||||
self.debug = debug
|
|
||||||
self.data = {}
|
self.data = {}
|
||||||
self.warning = None
|
self.warning = None
|
||||||
self.error = None
|
self.error = None
|
||||||
# if running as a context manager, self._context_mgr will be True
|
# if running as a context manager, self._context_mgr will be True
|
||||||
self._context_mgr = False
|
self._context_mgr = False
|
||||||
self._exiftoolproc = _ExifToolProc(exiftool=exiftool, debug=debug)
|
self._exiftoolproc = _ExifToolProc(exiftool=exiftool)
|
||||||
self._read_exif()
|
self._read_exif()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -366,9 +348,6 @@ class ExifTool:
|
|||||||
+ b"-execute\n"
|
+ b"-execute\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.debug:
|
|
||||||
logging.debug(f"running exiftool command: {command_str}")
|
|
||||||
|
|
||||||
# send the command
|
# send the command
|
||||||
self._process.stdin.write(command_str)
|
self._process.stdin.write(command_str)
|
||||||
self._process.stdin.flush()
|
self._process.stdin.flush()
|
||||||
@@ -389,10 +368,6 @@ class ExifTool:
|
|||||||
error = "" if error == b"" else error.decode("utf-8")
|
error = "" if error == b"" else error.decode("utf-8")
|
||||||
self.warning = warning
|
self.warning = warning
|
||||||
self.error = error
|
self.error = error
|
||||||
if self.debug:
|
|
||||||
logging.debug(
|
|
||||||
f"run_commands: output={output[:-EXIFTOOL_STAYOPEN_EOF_LEN]}, warning={warning}, error={error}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return output[:-EXIFTOOL_STAYOPEN_EOF_LEN], warning, error
|
return output[:-EXIFTOOL_STAYOPEN_EOF_LEN], warning, error
|
||||||
|
|
||||||
@@ -417,8 +392,6 @@ class ExifTool:
|
|||||||
"""
|
"""
|
||||||
json_str, _, _ = self.run_commands("-json")
|
json_str, _, _ = self.run_commands("-json")
|
||||||
if not json_str:
|
if not json_str:
|
||||||
if self.debug:
|
|
||||||
logging.debug(f"empty json_str")
|
|
||||||
return dict()
|
return dict()
|
||||||
json_str = unescape_str(json_str.decode("utf-8"))
|
json_str = unescape_str(json_str.decode("utf-8"))
|
||||||
|
|
||||||
@@ -427,8 +400,7 @@ class ExifTool:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
# will fail with some commands, e.g --ext AVI which produces
|
# will fail with some commands, e.g --ext AVI which produces
|
||||||
# 'No file with specified extension' instead of json
|
# 'No file with specified extension' instead of json
|
||||||
if self.debug:
|
logging.warning(f"error loading json returned by exiftool: {e} {json_str}")
|
||||||
logging.debug(f"json.loads error {e}")
|
|
||||||
return dict()
|
return dict()
|
||||||
exifdict = exifdict[0]
|
exifdict = exifdict[0]
|
||||||
if not tag_groups:
|
if not tag_groups:
|
||||||
@@ -442,25 +414,18 @@ class ExifTool:
|
|||||||
if normalized:
|
if normalized:
|
||||||
exifdict = {k.lower(): v for (k, v) in exifdict.items()}
|
exifdict = {k.lower(): v for (k, v) in exifdict.items()}
|
||||||
|
|
||||||
if self.debug:
|
|
||||||
logging.debug(f"asdict: {exifdict}")
|
|
||||||
|
|
||||||
return exifdict
|
return exifdict
|
||||||
|
|
||||||
def json(self):
|
def json(self):
|
||||||
"""returns JSON string containing all EXIF tags and values from exiftool"""
|
"""returns JSON string containing all EXIF tags and values from exiftool"""
|
||||||
json, _, _ = self.run_commands("-json")
|
json, _, _ = self.run_commands("-json")
|
||||||
json = unescape_str(json.decode("utf-8"))
|
json = unescape_str(json.decode("utf-8"))
|
||||||
if self.debug:
|
|
||||||
logging.debug(f"json: {json}")
|
|
||||||
return json
|
return json
|
||||||
|
|
||||||
def _read_exif(self):
|
def _read_exif(self):
|
||||||
"""read exif data from file"""
|
"""read exif data from file"""
|
||||||
data = self.asdict()
|
data = self.asdict()
|
||||||
self.data = {k: v for k, v in data.items()}
|
self.data = {k: v for k, v in data.items()}
|
||||||
if self.debug:
|
|
||||||
logging.debug(f"_read_exif: {self.data}")
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"file: {self.file}\nexiftool: {self._exiftoolproc._exiftool}"
|
return f"file: {self.file}\nexiftool: {self._exiftoolproc._exiftool}"
|
||||||
@@ -486,17 +451,15 @@ class ExifToolCaching(ExifTool):
|
|||||||
|
|
||||||
_singletons = {}
|
_singletons = {}
|
||||||
|
|
||||||
def __new__(cls, filepath, exiftool=None, debug=False):
|
def __new__(cls, filepath, exiftool=None):
|
||||||
"""create new object or return instance of already created singleton"""
|
"""create new object or return instance of already created singleton"""
|
||||||
if filepath not in cls._singletons:
|
if filepath not in cls._singletons:
|
||||||
cls._singletons[filepath] = _ExifToolCaching(
|
cls._singletons[filepath] = _ExifToolCaching(filepath, exiftool=exiftool)
|
||||||
filepath, exiftool=exiftool, debug=debug
|
|
||||||
)
|
|
||||||
return cls._singletons[filepath]
|
return cls._singletons[filepath]
|
||||||
|
|
||||||
|
|
||||||
class _ExifToolCaching(ExifTool):
|
class _ExifToolCaching(ExifTool):
|
||||||
def __init__(self, filepath, exiftool=None, debug=False):
|
def __init__(self, filepath, exiftool=None):
|
||||||
"""Create read-only ExifTool object that caches values
|
"""Create read-only ExifTool object that caches values
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -506,12 +469,9 @@ class _ExifToolCaching(ExifTool):
|
|||||||
Returns:
|
Returns:
|
||||||
ExifTool instance
|
ExifTool instance
|
||||||
"""
|
"""
|
||||||
self.debug = debug
|
|
||||||
self._json_cache = None
|
self._json_cache = None
|
||||||
self._asdict_cache = {}
|
self._asdict_cache = {}
|
||||||
super().__init__(
|
super().__init__(filepath, exiftool=exiftool, overwrite=False, flags=None)
|
||||||
filepath, exiftool=exiftool, overwrite=False, flags=None, debug=debug
|
|
||||||
)
|
|
||||||
|
|
||||||
def run_commands(self, *commands, no_file=False):
|
def run_commands(self, *commands, no_file=False):
|
||||||
if commands[0] not in ["-json", "-ver"]:
|
if commands[0] not in ["-json", "-ver"]:
|
||||||
|
|||||||
@@ -560,11 +560,15 @@ class PhotoExporter:
|
|||||||
touch_results = []
|
touch_results = []
|
||||||
for touch_file in set(touch_files):
|
for touch_file in set(touch_files):
|
||||||
ts = int(self.photo.date.timestamp())
|
ts = int(self.photo.date.timestamp())
|
||||||
stat = os.stat(touch_file)
|
try:
|
||||||
if stat.st_mtime != ts:
|
stat = os.stat(touch_file)
|
||||||
if not options.dry_run:
|
if stat.st_mtime != ts:
|
||||||
fileutil.utime(touch_file, (ts, ts))
|
fileutil.utime(touch_file, (ts, ts))
|
||||||
touch_results.append(touch_file)
|
touch_results.append(touch_file)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
# ignore errors if in dry_run as file may not be present
|
||||||
|
if not options.dry_run:
|
||||||
|
raise e from e
|
||||||
return ExportResults(touched=touch_results)
|
return ExportResults(touched=touch_results)
|
||||||
|
|
||||||
def _get_edited_filename(self, original_filename):
|
def _get_edited_filename(self, original_filename):
|
||||||
@@ -669,8 +673,8 @@ class PhotoExporter:
|
|||||||
|
|
||||||
if file_record.export_options != options.bit_flags:
|
if file_record.export_options != options.bit_flags:
|
||||||
# exporting with different set of options (e.g. exiftool), should update
|
# exporting with different set of options (e.g. exiftool), should update
|
||||||
# need to check this before exiftool in case exiftool options are different
|
# need to check this before exiftool in case exiftool options are different
|
||||||
# and export database is missing; this will always be True if database is missing
|
# and export database is missing; this will always be True if database is missing
|
||||||
# as it'll be None and bit_flags will be an int
|
# as it'll be None and bit_flags will be an int
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -588,7 +588,7 @@ class PhotoInfo:
|
|||||||
@property
|
@property
|
||||||
def ismissing(self):
|
def ismissing(self):
|
||||||
"""returns true if photo is missing from disk (which means it's not been downloaded from iCloud)
|
"""returns true if photo is missing from disk (which means it's not been downloaded from iCloud)
|
||||||
|
|
||||||
NOTE: the photos.db database uses an asynchrounous write-ahead log so changes in Photos
|
NOTE: the photos.db database uses an asynchrounous write-ahead log so changes in Photos
|
||||||
do not immediately get written to disk. In particular, I've noticed that downloading
|
do not immediately get written to disk. In particular, I've noticed that downloading
|
||||||
an image from the cloud does not force the database to be updated until something else
|
an image from the cloud does not force the database to be updated until something else
|
||||||
@@ -1343,7 +1343,7 @@ class PhotoInfo:
|
|||||||
try:
|
try:
|
||||||
exiftool_path = self._db._exiftool_path or get_exiftool_path()
|
exiftool_path = self._db._exiftool_path or get_exiftool_path()
|
||||||
if self.path is not None and os.path.isfile(self.path):
|
if self.path is not None and os.path.isfile(self.path):
|
||||||
exiftool = ExifToolCaching(self.path, exiftool=exiftool_path, debug=_debug())
|
exiftool = ExifToolCaching(self.path, exiftool=exiftool_path)
|
||||||
else:
|
else:
|
||||||
exiftool = None
|
exiftool = None
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from osxphotos.exiftool import get_exiftool_path
|
|
||||||
|
from osxphotos.exiftool import get_exiftool_path, unescape_str
|
||||||
|
|
||||||
TEST_FILE_ONE_KEYWORD = "tests/test-images/wedding.jpg"
|
TEST_FILE_ONE_KEYWORD = "tests/test-images/wedding.jpg"
|
||||||
TEST_FILE_BAD_IMAGE = "tests/test-images/badimage.jpeg"
|
TEST_FILE_BAD_IMAGE = "tests/test-images/badimage.jpeg"
|
||||||
@@ -89,6 +92,20 @@ EXIF_UUID_NO_GROUPS = {
|
|||||||
}
|
}
|
||||||
EXIF_UUID_NONE = ["A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C"]
|
EXIF_UUID_NONE = ["A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C"]
|
||||||
|
|
||||||
|
QUOTED_JSON_BYTES = b'[{"ExifTool:ExifToolVersion": 12.37,"ExifTool:Now": "2022:02:22 18:14:31+00:00","ExifTool:NewGUID": "20220222181431005A76C1A4B4D508A2","ExifTool:FileSequence": 0,"ExifTool:Warning": "Error running "xattr" to extract XAttr tags","ExifTool:ProcessingTime": 0.157028}]'
|
||||||
|
QUOTED_JSON_STRING_UNESCAPED = '[{"ExifTool:ExifToolVersion": 12.37,"ExifTool:Now": "2022:02:22 18:14:31+00:00","ExifTool:NewGUID": "20220222181431005A76C1A4B4D508A2","ExifTool:FileSequence": 0,"ExifTool:Warning": "Error running \\"xattr\\" to extract XAttr tags","ExifTool:ProcessingTime": 0.157028}]'
|
||||||
|
QUOTED_JSON_LOADED = [
|
||||||
|
{
|
||||||
|
"ExifTool:ExifToolVersion": 12.37,
|
||||||
|
"ExifTool:Now": "2022:02:22 18:14:31+00:00",
|
||||||
|
"ExifTool:NewGUID": "20220222181431005A76C1A4B4D508A2",
|
||||||
|
"ExifTool:FileSequence": 0,
|
||||||
|
"ExifTool:Warning": 'Error running "xattr" to extract XAttr tags',
|
||||||
|
"ExifTool:ProcessingTime": 0.157028,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
exiftool = get_exiftool_path()
|
exiftool = get_exiftool_path()
|
||||||
except:
|
except:
|
||||||
@@ -126,6 +143,7 @@ def test_setvalue_1():
|
|||||||
# test setting a tag value
|
# test setting a tag value
|
||||||
import os.path
|
import os.path
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import osxphotos.exiftool
|
import osxphotos.exiftool
|
||||||
from osxphotos.fileutil import FileUtil
|
from osxphotos.fileutil import FileUtil
|
||||||
|
|
||||||
@@ -145,6 +163,7 @@ def test_setvalue_multiline():
|
|||||||
# test setting a tag value with embedded newline
|
# test setting a tag value with embedded newline
|
||||||
import os.path
|
import os.path
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import osxphotos.exiftool
|
import osxphotos.exiftool
|
||||||
from osxphotos.fileutil import FileUtil
|
from osxphotos.fileutil import FileUtil
|
||||||
|
|
||||||
@@ -164,6 +183,7 @@ def test_setvalue_non_alphanumeric_chars():
|
|||||||
# test setting a tag value non-alphanumeric characters
|
# test setting a tag value non-alphanumeric characters
|
||||||
import os.path
|
import os.path
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import osxphotos.exiftool
|
import osxphotos.exiftool
|
||||||
from osxphotos.fileutil import FileUtil
|
from osxphotos.fileutil import FileUtil
|
||||||
|
|
||||||
@@ -183,6 +203,7 @@ def test_setvalue_warning():
|
|||||||
# test setting illegal tag value generates warning
|
# test setting illegal tag value generates warning
|
||||||
import os.path
|
import os.path
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import osxphotos.exiftool
|
import osxphotos.exiftool
|
||||||
from osxphotos.fileutil import FileUtil
|
from osxphotos.fileutil import FileUtil
|
||||||
|
|
||||||
@@ -199,6 +220,7 @@ def test_setvalue_error():
|
|||||||
# test setting tag on bad image generates error
|
# test setting tag on bad image generates error
|
||||||
import os.path
|
import os.path
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import osxphotos.exiftool
|
import osxphotos.exiftool
|
||||||
from osxphotos.fileutil import FileUtil
|
from osxphotos.fileutil import FileUtil
|
||||||
|
|
||||||
@@ -215,6 +237,7 @@ def test_setvalue_context_manager():
|
|||||||
# test setting a tag value as context manager
|
# test setting a tag value as context manager
|
||||||
import os.path
|
import os.path
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import osxphotos.exiftool
|
import osxphotos.exiftool
|
||||||
from osxphotos.fileutil import FileUtil
|
from osxphotos.fileutil import FileUtil
|
||||||
|
|
||||||
@@ -241,6 +264,7 @@ def test_setvalue_context_manager_warning():
|
|||||||
# test setting a tag value as context manager when warning generated
|
# test setting a tag value as context manager when warning generated
|
||||||
import os.path
|
import os.path
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import osxphotos.exiftool
|
import osxphotos.exiftool
|
||||||
from osxphotos.fileutil import FileUtil
|
from osxphotos.fileutil import FileUtil
|
||||||
|
|
||||||
@@ -257,6 +281,7 @@ def test_setvalue_context_manager_error():
|
|||||||
# test setting a tag value as context manager when error generated
|
# test setting a tag value as context manager when error generated
|
||||||
import os.path
|
import os.path
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import osxphotos.exiftool
|
import osxphotos.exiftool
|
||||||
from osxphotos.fileutil import FileUtil
|
from osxphotos.fileutil import FileUtil
|
||||||
|
|
||||||
@@ -273,6 +298,7 @@ def test_flags():
|
|||||||
# test that flags work
|
# test that flags work
|
||||||
import os.path
|
import os.path
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import osxphotos.exiftool
|
import osxphotos.exiftool
|
||||||
from osxphotos.fileutil import FileUtil
|
from osxphotos.fileutil import FileUtil
|
||||||
|
|
||||||
@@ -296,6 +322,7 @@ def test_clear_value():
|
|||||||
# test clearing a tag value
|
# test clearing a tag value
|
||||||
import os.path
|
import os.path
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import osxphotos.exiftool
|
import osxphotos.exiftool
|
||||||
from osxphotos.fileutil import FileUtil
|
from osxphotos.fileutil import FileUtil
|
||||||
|
|
||||||
@@ -315,6 +342,7 @@ def test_addvalues_1():
|
|||||||
# test setting a tag value
|
# test setting a tag value
|
||||||
import os.path
|
import os.path
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import osxphotos.exiftool
|
import osxphotos.exiftool
|
||||||
from osxphotos.fileutil import FileUtil
|
from osxphotos.fileutil import FileUtil
|
||||||
|
|
||||||
@@ -332,6 +360,7 @@ def test_addvalues_2():
|
|||||||
# test setting a tag value where multiple values already exist
|
# test setting a tag value where multiple values already exist
|
||||||
import os.path
|
import os.path
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import osxphotos.exiftool
|
import osxphotos.exiftool
|
||||||
from osxphotos.fileutil import FileUtil
|
from osxphotos.fileutil import FileUtil
|
||||||
|
|
||||||
@@ -353,6 +382,7 @@ def test_addvalues_non_alphanumeric_multiline():
|
|||||||
# test setting a tag value
|
# test setting a tag value
|
||||||
import os.path
|
import os.path
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import osxphotos.exiftool
|
import osxphotos.exiftool
|
||||||
from osxphotos.fileutil import FileUtil
|
from osxphotos.fileutil import FileUtil
|
||||||
|
|
||||||
@@ -373,6 +403,7 @@ def test_addvalues_unicode():
|
|||||||
# test setting a tag value with unicode
|
# test setting a tag value with unicode
|
||||||
import os.path
|
import os.path
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import osxphotos.exiftool
|
import osxphotos.exiftool
|
||||||
from osxphotos.fileutil import FileUtil
|
from osxphotos.fileutil import FileUtil
|
||||||
|
|
||||||
@@ -444,9 +475,10 @@ def test_as_dict_no_tag_groups():
|
|||||||
|
|
||||||
|
|
||||||
def test_json():
|
def test_json():
|
||||||
import osxphotos.exiftool
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
import osxphotos.exiftool
|
||||||
|
|
||||||
exif1 = osxphotos.exiftool.ExifTool(TEST_FILE_ONE_KEYWORD)
|
exif1 = osxphotos.exiftool.ExifTool(TEST_FILE_ONE_KEYWORD)
|
||||||
exifdata = json.loads(exif1.json())
|
exifdata = json.loads(exif1.json())
|
||||||
assert exifdata[0]["XMP:TagsList"] == "wedding"
|
assert exifdata[0]["XMP:TagsList"] == "wedding"
|
||||||
@@ -498,9 +530,10 @@ def test_photoinfo_exiftool_none():
|
|||||||
|
|
||||||
def test_exiftool_terminate():
|
def test_exiftool_terminate():
|
||||||
"""Test that exiftool process is terminated when exiftool.terminate() is called"""
|
"""Test that exiftool process is terminated when exiftool.terminate() is called"""
|
||||||
import osxphotos.exiftool
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
import osxphotos.exiftool
|
||||||
|
|
||||||
exif1 = osxphotos.exiftool.ExifTool(TEST_FILE_ONE_KEYWORD)
|
exif1 = osxphotos.exiftool.ExifTool(TEST_FILE_ONE_KEYWORD)
|
||||||
|
|
||||||
ps = subprocess.run(["ps"], capture_output=True)
|
ps = subprocess.run(["ps"], capture_output=True)
|
||||||
@@ -516,3 +549,11 @@ def test_exiftool_terminate():
|
|||||||
# verify we can create a new instance after termination
|
# verify we can create a new instance after termination
|
||||||
exif2 = osxphotos.exiftool.ExifTool(TEST_FILE_ONE_KEYWORD)
|
exif2 = osxphotos.exiftool.ExifTool(TEST_FILE_ONE_KEYWORD)
|
||||||
assert exif2.asdict()["IPTC:Keywords"] == "wedding"
|
assert exif2.asdict()["IPTC:Keywords"] == "wedding"
|
||||||
|
|
||||||
|
|
||||||
|
def test_unescape_str():
|
||||||
|
"""Test unescape_str, #636"""
|
||||||
|
quoted_str = unescape_str(QUOTED_JSON_BYTES.decode("utf-8"))
|
||||||
|
assert quoted_str == QUOTED_JSON_STRING_UNESCAPED
|
||||||
|
quoted_json = json.loads(quoted_str)
|
||||||
|
assert quoted_json == QUOTED_JSON_LOADED
|
||||||
|
|||||||
Reference in New Issue
Block a user