Added --exiftool-path to CLI

This commit is contained in:
Rhet Turnbull
2020-12-29 09:23:51 -08:00
parent 1dbf22fdc9
commit 4897fc4b05
8 changed files with 83 additions and 19 deletions

View File

@@ -363,6 +363,8 @@ Options:
(see also --ignore-date-modified); (see also --ignore-date-modified);
QuickTime:GPSCoordinates; QuickTime:GPSCoordinates;
UserData:GPSCoordinates. UserData:GPSCoordinates.
--exiftool-path EXIFTOOL_PATH Optionally specify path to exiftool; if not
provided, will look for exiftool in $PATH.
--exiftool-option OPTION Optional flag/option to pass to exiftool --exiftool-option OPTION Optional flag/option to pass to exiftool
when using --exiftool. For example, when using --exiftool. For example,
--exiftool-option '-m' to ignore minor --exiftool-option '-m' to ignore minor

View File

@@ -1157,8 +1157,9 @@ def query(
_list_libraries() _list_libraries()
return return
photosdb = osxphotos.PhotosDB(dbfile=db, verbose=verbose_)
photos = _query( photos = _query(
db=db, photosdb=photosdb,
keyword=keyword, keyword=keyword,
person=person, person=person,
album=album, album=album,
@@ -1383,6 +1384,12 @@ def query(
"(video files only): QuickTime:CreationDate; QuickTime:CreateDate; QuickTime:ModifyDate (see also --ignore-date-modified); " "(video files only): QuickTime:CreationDate; QuickTime:CreateDate; QuickTime:ModifyDate (see also --ignore-date-modified); "
"QuickTime:GPSCoordinates; UserData:GPSCoordinates.", "QuickTime:GPSCoordinates; UserData:GPSCoordinates.",
) )
@click.option(
"--exiftool-path",
metavar="EXIFTOOL_PATH",
type=click.Path(exists=True),
help="Optionally specify path to exiftool; if not provided, will look for exiftool in $PATH.",
)
@click.option( @click.option(
"--exiftool-option", "--exiftool-option",
multiple=True, multiple=True,
@@ -1601,6 +1608,7 @@ def export(
download_missing, download_missing,
dest, dest,
exiftool, exiftool,
exiftool_path,
exiftool_option, exiftool_option,
exiftool_merge_keywords, exiftool_merge_keywords,
exiftool_merge_persons, exiftool_merge_persons,
@@ -1735,6 +1743,7 @@ def export(
not_live = cfg.not_live not_live = cfg.not_live
download_missing = cfg.download_missing download_missing = cfg.download_missing
exiftool = cfg.exiftool exiftool = cfg.exiftool
exiftool_path = cfg.exiftool_path
exiftool_option = cfg.exiftool_option exiftool_option = cfg.exiftool_option
exiftool_merge_keywords = cfg.exiftool_merge_keywords exiftool_merge_keywords = cfg.exiftool_merge_keywords
exiftool_merge_persons = cfg.exiftool_merge_persons exiftool_merge_persons = cfg.exiftool_merge_persons
@@ -1813,6 +1822,7 @@ def export(
("exiftool_option", ("exiftool")), ("exiftool_option", ("exiftool")),
("exiftool_merge_keywords", ("exiftool", "sidecar")), ("exiftool_merge_keywords", ("exiftool", "sidecar")),
("exiftool_merge_persons", ("exiftool", "sidecar")), ("exiftool_merge_persons", ("exiftool", "sidecar")),
("exiftool_path", ("exiftool")),
] ]
try: try:
cfg.validate(exclusive=exclusive_options, dependent=dependent_options, cli=True) cfg.validate(exclusive=exclusive_options, dependent=dependent_options, cli=True)
@@ -1881,10 +1891,10 @@ def export(
not x for x in [skip_edited, skip_bursts, skip_live, skip_raw] not x for x in [skip_edited, skip_bursts, skip_live, skip_raw]
] ]
# verify exiftool installed an in path # verify exiftool installed and in path if path not provided
if exiftool: if exiftool and not exiftool_path:
try: try:
_ = get_exiftool_path() exiftool_path = get_exiftool_path()
except FileNotFoundError: except FileNotFoundError:
click.echo( click.echo(
click.style( click.style(
@@ -1896,6 +1906,9 @@ def export(
) )
ctx.exit(2) ctx.exit(2)
if exiftool:
verbose_(f"exiftool path: {exiftool_path}")
isphoto = ismovie = True # default searches for everything isphoto = ismovie = True # default searches for everything
if only_movies: if only_movies:
isphoto = False isphoto = False
@@ -1977,8 +1990,9 @@ def export(
f"Upgraded export database {export_db_path} from version {upgraded[0]} to {upgraded[1]}" f"Upgraded export database {export_db_path} from version {upgraded[0]} to {upgraded[1]}"
) )
photosdb = osxphotos.PhotosDB(dbfile=db, verbose=verbose_, exiftool=exiftool_path)
photos = _query( photos = _query(
db=db, photosdb=photosdb,
keyword=keyword, keyword=keyword,
person=person, person=person,
album=album, album=album,
@@ -2327,7 +2341,7 @@ def print_photo_info(photos, json=False):
def _query( def _query(
db=None, photosdb,
keyword=None, keyword=None,
person=None, person=None,
album=None, album=None,
@@ -2386,12 +2400,12 @@ def _query(
has_likes=False, has_likes=False,
no_likes=False, no_likes=False,
): ):
"""run a query against PhotosDB to extract the photos based on user supply criteria """ Run a query against PhotosDB to extract the photos based on user supply criteria used by query and export commands
used by query and export commands
arguments must be passed in same order as query and export Args:
if either is modified, need to ensure all three functions are updated""" photosdb: PhotosDB object
"""
photosdb = osxphotos.PhotosDB(dbfile=db, verbose=verbose_)
if deleted or deleted_only: if deleted or deleted_only:
photos = photosdb.photos( photos = photosdb.photos(
uuid=uuid, uuid=uuid,

View File

@@ -1,5 +1,5 @@
""" version info """ """ version info """
__version__ = "0.38.19" __version__ = "0.38.20"

View File

@@ -49,7 +49,7 @@ class _ExifToolProc:
if hasattr(self, "_process_running") and self._process_running: if hasattr(self, "_process_running") and self._process_running:
# already running # already running
if exiftool is not None: if exiftool != self._exiftool:
logging.warning( logging.warning(
f"exiftool subprocess already running, " f"exiftool subprocess already running, "
f"ignoring exiftool={exiftool}" f"ignoring exiftool={exiftool}"

View File

@@ -18,12 +18,11 @@ def exiftool(self):
return self._exiftool return self._exiftool
except AttributeError: except AttributeError:
try: try:
exiftool_path = 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 = ExifTool(self.path) exiftool = ExifTool(self.path, exiftool=exiftool_path)
else: else:
exiftool = None exiftool = None
logging.debug(f"exiftool: missing path {self.uuid}")
except FileNotFoundError: except FileNotFoundError:
# get_exiftool_path raises FileNotFoundError if exiftool not found # get_exiftool_path raises FileNotFoundError if exiftool not found
exiftool = None exiftool = None

View File

@@ -1358,7 +1358,7 @@ def _write_exif_data(
merge_exif_persons=merge_exif_persons, merge_exif_persons=merge_exif_persons,
) )
with ExifTool(filepath, flags=flags) as exiftool: with ExifTool(filepath, flags=flags, exiftool=self._db._exiftool_path) as exiftool:
for exiftag, val in exif_info.items(): for exiftag, val in exif_info.items():
if type(val) == list: if type(val) == list:
for v in val: for v in val:

View File

@@ -70,12 +70,13 @@ class PhotosDB:
from ._photosdb_process_scoreinfo import _process_scoreinfo from ._photosdb_process_scoreinfo import _process_scoreinfo
from ._photosdb_process_comments import _process_comments from ._photosdb_process_comments import _process_comments
def __init__(self, dbfile=None, verbose=None): def __init__(self, dbfile=None, verbose=None, exiftool=None):
""" Create a new PhotosDB object. """ Create a new PhotosDB object.
Args: Args:
dbfile: specify full path to photos library or photos.db; if None, will attempt to locate last library opened by Photos. dbfile: specify full path to photos library or photos.db; if None, will attempt to locate last library opened by Photos.
verbose: optional callable function to use for printing verbose text during processing; if None (default), does not print output. verbose: optional callable function to use for printing verbose text during processing; if None (default), does not print output.
exiftool: optional path to exiftool for methods that require this (e.g. PhotoInfo.exiftool); if not provided, will search PATH
Raises: Raises:
FileNotFoundError if dbfile is not a valid Photos library. FileNotFoundError if dbfile is not a valid Photos library.
@@ -98,6 +99,8 @@ class PhotosDB:
raise TypeError("verbose must be callable") raise TypeError("verbose must be callable")
self._verbose = verbose self._verbose = verbose
self._exiftool_path = exiftool
# create a temporary directory # create a temporary directory
# tempfile.TemporaryDirectory gets cleaned up when the object does # tempfile.TemporaryDirectory gets cleaned up when the object does
self._tempdir = tempfile.TemporaryDirectory(prefix="osxphotos_") self._tempdir = tempfile.TemporaryDirectory(prefix="osxphotos_")

View File

@@ -1055,6 +1055,52 @@ def test_export_exiftool():
assert exif[key] == CLI_EXIFTOOL[uuid][key] assert exif[key] == CLI_EXIFTOOL[uuid][key]
@pytest.mark.skipif(exiftool is None, reason="exiftool not installed")
def test_export_exiftool_path():
""" test --exiftool with --exiftool-path """
import glob
import os
import os.path
import shutil
import tempfile
from osxphotos.__main__ import export
from osxphotos.exiftool import ExifTool, get_exiftool_path
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
tempdir = tempfile.TemporaryDirectory()
exiftool_source = get_exiftool_path()
exiftool_path = os.path.join(tempdir.name, "myexiftool")
shutil.copy2(exiftool_source, exiftool_path)
for uuid in CLI_EXIFTOOL:
result = runner.invoke(
export,
[
os.path.join(cwd, PHOTOS_DB_15_6),
".",
"-V",
"--exiftool",
"--uuid",
f"{uuid}",
"--exiftool-path",
exiftool_path,
],
)
assert result.exit_code == 0
assert f"exiftool path: {exiftool_path}" in result.output
files = glob.glob("*")
assert sorted(files) == sorted([CLI_EXIFTOOL[uuid]["File:FileName"]])
exif = ExifTool(CLI_EXIFTOOL[uuid]["File:FileName"]).asdict()
for key in CLI_EXIFTOOL[uuid]:
if type(exif[key]) == list:
assert sorted(exif[key]) == sorted(CLI_EXIFTOOL[uuid][key])
else:
assert exif[key] == CLI_EXIFTOOL[uuid][key]
@pytest.mark.skipif(exiftool is None, reason="exiftool not installed") @pytest.mark.skipif(exiftool is None, reason="exiftool not installed")
def test_export_exiftool_ignore_date_modified(): def test_export_exiftool_ignore_date_modified():
import glob import glob