Added better exiftool error handling, closes #300
This commit is contained in:
@@ -19,6 +19,8 @@ from ._constants import (
|
||||
_EXIF_TOOL_URL,
|
||||
_PHOTOS_4_VERSION,
|
||||
_UNKNOWN_PLACE,
|
||||
CLI_COLOR_ERROR,
|
||||
CLI_COLOR_WARNING,
|
||||
DEFAULT_JPEG_QUALITY,
|
||||
DEFAULT_EDITED_SUFFIX,
|
||||
DEFAULT_ORIGINAL_SUFFIX,
|
||||
@@ -50,7 +52,15 @@ OSXPHOTOS_EXPORT_DB = ".osxphotos_export.db"
|
||||
def verbose_(*args, **kwargs):
|
||||
""" print output if verbose flag set """
|
||||
if VERBOSE:
|
||||
click.echo(*args, **kwargs)
|
||||
styled_args = []
|
||||
for arg in args:
|
||||
if type(arg) == str:
|
||||
if "error" in arg.lower():
|
||||
arg = click.style(arg, fg=CLI_COLOR_ERROR)
|
||||
elif "warning" in arg.lower():
|
||||
arg = click.style(arg, fg=CLI_COLOR_WARNING)
|
||||
styled_args.append(arg)
|
||||
click.echo(*styled_args, **kwargs)
|
||||
|
||||
|
||||
def normalize_unicode(value):
|
||||
@@ -1605,7 +1615,11 @@ def export(
|
||||
cfg.load_from_file(load_config)
|
||||
except ConfigOptionsLoadError as e:
|
||||
click.echo(
|
||||
f"Error parsing {load_config} config file: {e.message}", err=True
|
||||
click.style(
|
||||
f"Error parsing {load_config} config file: {e.message}",
|
||||
fg=CLI_COLOR_ERROR,
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
raise click.Abort()
|
||||
|
||||
@@ -1737,7 +1751,12 @@ def export(
|
||||
try:
|
||||
cfg.validate(exclusive=exclusive_options, dependent=dependent_options, cli=True)
|
||||
except ConfigOptionsInvalidError as e:
|
||||
click.echo(f"Incompatible export options: {e.message}", err=True)
|
||||
click.echo(
|
||||
click.style(
|
||||
f"Incompatible export options: {e.message}", fg=CLI_COLOR_ERROR
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
raise click.Abort()
|
||||
|
||||
if save_config:
|
||||
@@ -1752,13 +1771,20 @@ def export(
|
||||
)
|
||||
|
||||
if not os.path.isdir(dest):
|
||||
click.echo(f"DEST {dest} must be valid path", err=True)
|
||||
click.echo(
|
||||
click.style(f"DEST {dest} must be valid path", fg=CLI_COLOR_ERROR), err=True
|
||||
)
|
||||
raise click.Abort()
|
||||
|
||||
dest = str(pathlib.Path(dest).resolve())
|
||||
|
||||
if report and os.path.isdir(report):
|
||||
click.echo(f"report is a directory, must be file name", err=True)
|
||||
click.echo(
|
||||
click.style(
|
||||
f"report is a directory, must be file name", fg=CLI_COLOR_ERROR
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
raise click.Abort()
|
||||
|
||||
# if use_photokit and not check_photokit_authorization():
|
||||
@@ -1785,8 +1811,11 @@ def export(
|
||||
_ = get_exiftool_path()
|
||||
except FileNotFoundError:
|
||||
click.echo(
|
||||
"Could not find exiftool. Please download and install"
|
||||
" from https://exiftool.org/",
|
||||
click.style(
|
||||
"Could not find exiftool. Please download and install"
|
||||
" from https://exiftool.org/",
|
||||
fg=CLI_COLOR_ERROR,
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
ctx.exit(2)
|
||||
@@ -1819,10 +1848,13 @@ def export(
|
||||
other_db_files = find_files_in_branch(dest, OSXPHOTOS_EXPORT_DB)
|
||||
if other_db_files:
|
||||
click.echo(
|
||||
"WARNING: found other export database files in this destination directory branch. "
|
||||
+ "This likely means you are attempting to export files into a directory "
|
||||
+ "that is either the parent or a child directory of a previous export. "
|
||||
+ "Proceeding may cause your exported files to be overwritten.",
|
||||
click.style(
|
||||
"WARNING: found other export database files in this destination directory branch. "
|
||||
+ "This likely means you are attempting to export files into a directory "
|
||||
+ "that is either the parent or a child directory of a previous export. "
|
||||
+ "Proceeding may cause your exported files to be overwritten.",
|
||||
fg=CLI_COLOR_WARNING,
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
click.echo(
|
||||
@@ -1929,22 +1961,10 @@ def export(
|
||||
# because the original code used --original-name as an option
|
||||
original_name = not current_name
|
||||
|
||||
results_exported = []
|
||||
results_new = []
|
||||
results_updated = []
|
||||
results_skipped = []
|
||||
results_exif_updated = []
|
||||
results_touched = []
|
||||
results_converted = []
|
||||
results_sidecar_json_written = []
|
||||
results_sidecar_json_skipped = []
|
||||
results_sidecar_xmp_written = []
|
||||
results_sidecar_xmp_skipped = []
|
||||
results_missing = []
|
||||
results_error = []
|
||||
results = ExportResults()
|
||||
if verbose:
|
||||
for p in photos:
|
||||
results = export_photo(
|
||||
export_results = export_photo(
|
||||
photo=p,
|
||||
dest=dest,
|
||||
verbose=verbose,
|
||||
@@ -1979,19 +1999,7 @@ def export(
|
||||
ignore_date_modified=ignore_date_modified,
|
||||
use_photokit=use_photokit,
|
||||
)
|
||||
results_exported.extend(results.exported)
|
||||
results_new.extend(results.new)
|
||||
results_updated.extend(results.updated)
|
||||
results_skipped.extend(results.skipped)
|
||||
results_exif_updated.extend(results.exif_updated)
|
||||
results_touched.extend(results.touched)
|
||||
results_converted.extend(results.converted_to_jpeg)
|
||||
results_sidecar_json_written.extend(results.sidecar_json_written)
|
||||
results_sidecar_json_skipped.extend(results.sidecar_json_skipped)
|
||||
results_sidecar_xmp_written.extend(results.sidecar_xmp_written)
|
||||
results_sidecar_xmp_skipped.extend(results.sidecar_xmp_skipped)
|
||||
results_missing.extend(results.missing)
|
||||
results_error.extend(results.error)
|
||||
results += export_results
|
||||
|
||||
# if convert_to_jpeg and p.isphoto and p.uti != "public.jpeg":
|
||||
# for photo_file in set(
|
||||
@@ -2003,7 +2011,7 @@ def export(
|
||||
# show progress bar
|
||||
with click.progressbar(photos) as bar:
|
||||
for p in bar:
|
||||
results = export_photo(
|
||||
export_results = export_photo(
|
||||
photo=p,
|
||||
dest=dest,
|
||||
verbose=verbose,
|
||||
@@ -2038,19 +2046,7 @@ def export(
|
||||
ignore_date_modified=ignore_date_modified,
|
||||
use_photokit=use_photokit,
|
||||
)
|
||||
results_exported.extend(results.exported)
|
||||
results_new.extend(results.new)
|
||||
results_updated.extend(results.updated)
|
||||
results_skipped.extend(results.skipped)
|
||||
results_exif_updated.extend(results.exif_updated)
|
||||
results_touched.extend(results.touched)
|
||||
results_converted.extend(results.converted_to_jpeg)
|
||||
results_sidecar_json_written.extend(results.sidecar_json_written)
|
||||
results_sidecar_json_skipped.extend(results.sidecar_json_skipped)
|
||||
results_sidecar_xmp_written.extend(results.sidecar_xmp_written)
|
||||
results_sidecar_xmp_skipped.extend(results.sidecar_xmp_skipped)
|
||||
results_missing.extend(results.missing)
|
||||
results_error.extend(results.error)
|
||||
results += export_results
|
||||
|
||||
# print summary results
|
||||
# print(f"results_exported: {results_exported}")
|
||||
@@ -2069,19 +2065,19 @@ def export(
|
||||
|
||||
if cleanup:
|
||||
all_files = (
|
||||
results_exported
|
||||
+ results_skipped
|
||||
+ results_exif_updated
|
||||
+ results_touched
|
||||
+ results_converted
|
||||
+ results_sidecar_json_written
|
||||
+ results_sidecar_json_skipped
|
||||
+ results_sidecar_xmp_written
|
||||
+ results_sidecar_xmp_skipped
|
||||
results.exported
|
||||
+ results.skipped
|
||||
+ results.exif_updated
|
||||
+ results.touched
|
||||
+ results.converted_to_jpeg
|
||||
+ results.sidecar_json_written
|
||||
+ results.sidecar_json_skipped
|
||||
+ results.sidecar_xmp_written
|
||||
+ results.sidecar_xmp_skipped
|
||||
# include missing so a file that was already in export directory
|
||||
# but was missing on --update doesn't get deleted
|
||||
# (better to have old version than none)
|
||||
+ results_missing
|
||||
+ results.missing
|
||||
+ [str(pathlib.Path(export_db_path).resolve())]
|
||||
)
|
||||
click.echo(f"Cleaning up {dest}")
|
||||
@@ -2092,41 +2088,26 @@ def export(
|
||||
|
||||
if report:
|
||||
verbose_(f"Writing export report to {report}")
|
||||
write_export_report(
|
||||
report,
|
||||
results_exported=results_exported,
|
||||
results_new=results_new,
|
||||
results_updated=results_updated,
|
||||
results_skipped=results_skipped,
|
||||
results_exif_updated=results_exif_updated,
|
||||
results_touched=results_touched,
|
||||
results_converted=results_converted,
|
||||
results_sidecar_json_written=results_sidecar_json_written,
|
||||
results_sidecar_json_skipped=results_sidecar_json_skipped,
|
||||
results_sidecar_xmp_written=results_sidecar_xmp_written,
|
||||
results_sidecar_xmp_skipped=results_sidecar_xmp_skipped,
|
||||
results_missing=results_missing,
|
||||
results_error=results_error,
|
||||
)
|
||||
write_export_report(report, results)
|
||||
|
||||
photo_str_total = "photos" if len(photos) != 1 else "photo"
|
||||
if update:
|
||||
summary = (
|
||||
f"Processed: {len(photos)} {photo_str_total}, "
|
||||
f"exported: {len(results_new)}, "
|
||||
f"updated: {len(results_updated)}, "
|
||||
f"skipped: {len(results_skipped)}, "
|
||||
f"updated EXIF data: {len(results_exif_updated)}, "
|
||||
f"exported: {len(results.new)}, "
|
||||
f"updated: {len(results.updated)}, "
|
||||
f"skipped: {len(results.skipped)}, "
|
||||
f"updated EXIF data: {len(results.exif_updated)}, "
|
||||
)
|
||||
else:
|
||||
summary = (
|
||||
f"Processed: {len(photos)} {photo_str_total}, "
|
||||
f"exported: {len(results_exported)}, "
|
||||
f"exported: {len(results.exported)}, "
|
||||
)
|
||||
summary += f"missing: {len(results_missing)}, "
|
||||
summary += f"error: {len(results_error)}"
|
||||
summary += f"missing: {len(results.missing)}, "
|
||||
summary += f"error: {len(results.error)}"
|
||||
if touch_file:
|
||||
summary += f", touched date: {len(results_touched)}"
|
||||
summary += f", touched date: {len(results.touched)}"
|
||||
click.echo(summary)
|
||||
stop_time = time.perf_counter()
|
||||
click.echo(f"Elapsed time: {(stop_time-start_time):.3f} seconds")
|
||||
@@ -2651,19 +2632,7 @@ def export_photo(
|
||||
global VERBOSE
|
||||
VERBOSE = bool(verbose)
|
||||
|
||||
results_exported = []
|
||||
results_new = []
|
||||
results_updated = []
|
||||
results_skipped = []
|
||||
results_exif_updated = []
|
||||
results_touched = []
|
||||
results_converted = []
|
||||
results_sidecar_json_written = []
|
||||
results_sidecar_json_skipped = []
|
||||
results_sidecar_xmp_written = []
|
||||
results_sidecar_xmp_skipped = []
|
||||
results_error = []
|
||||
results_missing = []
|
||||
results = ExportResults()
|
||||
|
||||
export_original = not (skip_original_if_edited and photo.hasadjustments)
|
||||
|
||||
@@ -2767,7 +2736,7 @@ def export_photo(
|
||||
if missing_original:
|
||||
space = " " if not verbose else ""
|
||||
verbose_(f"{space}Skipping missing photo {photo.original_filename}")
|
||||
results_missing.append(
|
||||
results.missing.append(
|
||||
str(pathlib.Path(dest_path) / original_filename)
|
||||
)
|
||||
else:
|
||||
@@ -2799,33 +2768,21 @@ def export_photo(
|
||||
use_photokit=use_photokit,
|
||||
verbose=verbose_,
|
||||
)
|
||||
results += export_results
|
||||
for warning_ in export_results.exiftool_warning:
|
||||
verbose_(f"exiftool warning for file {warning_[0]}: {warning_[1]}")
|
||||
for error_ in export_results.exiftool_error:
|
||||
click.echo(click.style(f"exiftool error for file {error_[0]}: {error_[1]}", fg=CLI_COLOR_ERROR),err=True)
|
||||
|
||||
results_exported.extend(export_results.exported)
|
||||
results_new.extend(export_results.new)
|
||||
results_updated.extend(export_results.updated)
|
||||
results_skipped.extend(export_results.skipped)
|
||||
results_exif_updated.extend(export_results.exif_updated)
|
||||
results_touched.extend(export_results.touched)
|
||||
results_converted.extend(export_results.converted_to_jpeg)
|
||||
results_sidecar_json_written.extend(
|
||||
export_results.sidecar_json_written
|
||||
)
|
||||
results_sidecar_json_skipped.extend(
|
||||
export_results.sidecar_json_skipped
|
||||
)
|
||||
results_sidecar_xmp_written.extend(
|
||||
export_results.sidecar_xmp_written
|
||||
)
|
||||
results_sidecar_xmp_skipped.extend(
|
||||
export_results.sidecar_xmp_skipped
|
||||
)
|
||||
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
click.echo(
|
||||
f"Error exporting photo {photo.original_filename} ({photo.filename}) as {original_filename}",
|
||||
click.style(
|
||||
f"Error exporting photo {photo.original_filename} ({photo.filename}) as {original_filename}: {e}",
|
||||
fg=CLI_COLOR_ERROR,
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
results_error.extend(
|
||||
results.error.append(
|
||||
str(pathlib.Path(dest) / original_filename)
|
||||
)
|
||||
else:
|
||||
@@ -2875,7 +2832,7 @@ def export_photo(
|
||||
if missing_edited:
|
||||
space = " " if not verbose else ""
|
||||
verbose_(f"{space}Skipping missing edited photo for {filename}")
|
||||
results_missing.append(
|
||||
results.missing.append(
|
||||
str(pathlib.Path(dest_path) / edited_filename)
|
||||
)
|
||||
else:
|
||||
@@ -2906,65 +2863,36 @@ def export_photo(
|
||||
use_photokit=use_photokit,
|
||||
verbose=verbose_,
|
||||
)
|
||||
|
||||
results_exported.extend(export_results_edited.exported)
|
||||
results_new.extend(export_results_edited.new)
|
||||
results_updated.extend(export_results_edited.updated)
|
||||
results_skipped.extend(export_results_edited.skipped)
|
||||
results_exif_updated.extend(export_results_edited.exif_updated)
|
||||
results_touched.extend(export_results_edited.touched)
|
||||
results_converted.extend(
|
||||
export_results_edited.converted_to_jpeg
|
||||
)
|
||||
results_sidecar_json_written.extend(
|
||||
export_results_edited.sidecar_json_written
|
||||
)
|
||||
results_sidecar_json_skipped.extend(
|
||||
export_results_edited.sidecar_json_skipped
|
||||
)
|
||||
results_sidecar_xmp_written.extend(
|
||||
export_results_edited.sidecar_xmp_written
|
||||
)
|
||||
results_sidecar_xmp_skipped.extend(
|
||||
export_results_edited.sidecar_xmp_skipped
|
||||
)
|
||||
|
||||
except Exception:
|
||||
results += export_results_edited
|
||||
for warning_ in export_results_edited.exiftool_warning:
|
||||
verbose_(f"exiftool warning for file {warning_[0]}: {warning_[1]}")
|
||||
for error_ in export_results_edited.exiftool_error:
|
||||
click.echo(click.style(f"exiftool error for file {error_[0]}: {error_[1]}", fg=CLI_COLOR_ERROR),err=True)
|
||||
except Exception as e:
|
||||
click.echo(
|
||||
f"Error exporting photo {filename} as {edited_filename}",
|
||||
click.style(
|
||||
f"Error exporting photo {filename} as {edited_filename}",
|
||||
fg=CLI_COLOR_ERROR,
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
results_error.extend(str(pathlib.Path(dest) / edited_filename))
|
||||
results.error.append(str(pathlib.Path(dest) / edited_filename))
|
||||
|
||||
if verbose:
|
||||
if update:
|
||||
for new in results_new:
|
||||
for new in results.new:
|
||||
verbose_(f"Exported new file {new}")
|
||||
for updated in results_updated:
|
||||
for updated in results.updated:
|
||||
verbose_(f"Exported updated file {updated}")
|
||||
for skipped in results_skipped:
|
||||
for skipped in results.skipped:
|
||||
verbose_(f"Skipped up to date file {skipped}")
|
||||
else:
|
||||
for exported in results_exported:
|
||||
for exported in results.exported:
|
||||
verbose_(f"Exported {exported}")
|
||||
for touched in results_touched:
|
||||
for touched in results.touched:
|
||||
verbose_(f"Touched date on file {touched}")
|
||||
|
||||
return ExportResults(
|
||||
exported=results_exported,
|
||||
new=results_new,
|
||||
updated=results_updated,
|
||||
skipped=results_skipped,
|
||||
exif_updated=results_exif_updated,
|
||||
touched=results_touched,
|
||||
converted_to_jpeg=results_converted,
|
||||
sidecar_json_written=results_sidecar_json_written,
|
||||
sidecar_json_skipped=results_sidecar_json_skipped,
|
||||
sidecar_xmp_written=results_sidecar_xmp_written,
|
||||
sidecar_xmp_skipped=results_sidecar_xmp_skipped,
|
||||
missing=results_missing,
|
||||
error=results_error,
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
def get_filenames_from_template(photo, filename_template, original_name):
|
||||
@@ -3115,24 +3043,14 @@ def load_uuid_from_file(filename):
|
||||
return uuid
|
||||
|
||||
|
||||
def write_export_report(
|
||||
report_file,
|
||||
results_exported,
|
||||
results_new,
|
||||
results_updated,
|
||||
results_skipped,
|
||||
results_exif_updated,
|
||||
results_touched,
|
||||
results_converted,
|
||||
results_sidecar_json_written,
|
||||
results_sidecar_json_skipped,
|
||||
results_sidecar_xmp_written,
|
||||
results_sidecar_xmp_skipped,
|
||||
results_missing,
|
||||
results_error,
|
||||
):
|
||||
def write_export_report(report_file, results):
|
||||
|
||||
""" write CSV report with results from export """
|
||||
""" write CSV report with results from export
|
||||
|
||||
Args:
|
||||
report_file: path to report file
|
||||
results: ExportResults object
|
||||
"""
|
||||
|
||||
# Collect results for reporting
|
||||
# TODO: pull this in a separate write_report function
|
||||
@@ -3150,65 +3068,61 @@ def write_export_report(
|
||||
"sidecar_json": 0,
|
||||
"missing": 0,
|
||||
"error": 0,
|
||||
"exiftool_warning": "",
|
||||
"exiftool_error": "",
|
||||
}
|
||||
for result in results_exported
|
||||
+ results_new
|
||||
+ results_updated
|
||||
+ results_skipped
|
||||
+ results_exif_updated
|
||||
+ results_touched
|
||||
+ results_converted
|
||||
+ results_sidecar_json_written
|
||||
+ results_sidecar_json_skipped
|
||||
+ results_sidecar_xmp_written
|
||||
+ results_sidecar_xmp_skipped
|
||||
+ results_missing
|
||||
+ results_error
|
||||
for result in results.all_files()
|
||||
}
|
||||
|
||||
for result in results_exported:
|
||||
for result in results.exported:
|
||||
all_results[result]["exported"] = 1
|
||||
|
||||
for result in results_new:
|
||||
for result in results.new:
|
||||
all_results[result]["new"] = 1
|
||||
|
||||
for result in results_updated:
|
||||
for result in results.updated:
|
||||
all_results[result]["updated"] = 1
|
||||
|
||||
for result in results_skipped:
|
||||
for result in results.skipped:
|
||||
all_results[result]["skipped"] = 1
|
||||
|
||||
for result in results_exif_updated:
|
||||
for result in results.exif_updated:
|
||||
all_results[result]["exif_updated"] = 1
|
||||
|
||||
for result in results_touched:
|
||||
for result in results.touched:
|
||||
all_results[result]["touched"] = 1
|
||||
|
||||
for result in results_converted:
|
||||
for result in results.converted_to_jpeg:
|
||||
all_results[result]["converted_to_jpeg"] = 1
|
||||
|
||||
for result in results_sidecar_xmp_written:
|
||||
for result in results.sidecar_xmp_written:
|
||||
all_results[result]["sidecar_xmp"] = 1
|
||||
all_results[result]["exported"] = 1
|
||||
|
||||
for result in results_sidecar_xmp_skipped:
|
||||
for result in results.sidecar_xmp_skipped:
|
||||
all_results[result]["sidecar_xmp"] = 1
|
||||
all_results[result]["skipped"] = 1
|
||||
|
||||
for result in results_sidecar_json_written:
|
||||
for result in results.sidecar_json_written:
|
||||
all_results[result]["sidecar_json"] = 1
|
||||
all_results[result]["exported"] = 1
|
||||
|
||||
for result in results_sidecar_json_skipped:
|
||||
for result in results.sidecar_json_skipped:
|
||||
all_results[result]["sidecar_json"] = 1
|
||||
all_results[result]["skipped"] = 1
|
||||
|
||||
for result in results_missing:
|
||||
for result in results.missing:
|
||||
all_results[result]["missing"] = 1
|
||||
|
||||
for result in results_error:
|
||||
for result in results.error:
|
||||
all_results[result]["error"] = 1
|
||||
|
||||
for result in results.exiftool_warning:
|
||||
all_results[result[0]]["exiftool_warning"] = result[1]
|
||||
|
||||
for result in results.exiftool_error:
|
||||
all_results[result[0]]["exiftool_error"] = result[1]
|
||||
|
||||
report_columns = [
|
||||
"filename",
|
||||
"exported",
|
||||
@@ -3222,6 +3136,8 @@ def write_export_report(
|
||||
"sidecar_json",
|
||||
"missing",
|
||||
"error",
|
||||
"exiftool_warning",
|
||||
"exiftool_error",
|
||||
]
|
||||
|
||||
try:
|
||||
@@ -3231,7 +3147,10 @@ def write_export_report(
|
||||
for data in [result for result in all_results.values()]:
|
||||
writer.writerow(data)
|
||||
except IOError:
|
||||
click.echo("Could not open output file for writing", err=True)
|
||||
click.echo(
|
||||
click.style("Could not open output file for writing", fg=CLI_COLOR_ERROR),
|
||||
err=True,
|
||||
)
|
||||
raise click.Abort()
|
||||
|
||||
|
||||
|
||||
@@ -118,4 +118,6 @@ DEFAULT_EDITED_SUFFIX = "_edited"
|
||||
# Default suffix to add to original images
|
||||
DEFAULT_ORIGINAL_SUFFIX = ""
|
||||
|
||||
|
||||
# Colors for print CLI messages
|
||||
CLI_COLOR_ERROR = 'red'
|
||||
CLI_COLOR_WARNING = 'yellow'
|
||||
|
||||
@@ -145,6 +145,7 @@ class ExifTool:
|
||||
self.file = filepath
|
||||
self.overwrite = overwrite
|
||||
self.data = {}
|
||||
self.warning = None
|
||||
self.error = None
|
||||
# if running as a context manager, self._context_mgr will be True
|
||||
self._context_mgr = False
|
||||
@@ -163,6 +164,7 @@ class ExifTool:
|
||||
True if success otherwise False
|
||||
|
||||
If error generated by exiftool, returns False and sets self.error to error string
|
||||
If warning generated by exiftool, returns True (unless there was also an error) and sets self.warning to warning string
|
||||
If called in context manager, returns True (execution is delayed until exiting context manager)
|
||||
"""
|
||||
|
||||
@@ -175,8 +177,8 @@ class ExifTool:
|
||||
self._commands.extend(command)
|
||||
return True
|
||||
else:
|
||||
_, self.error = self.run_commands(*command)
|
||||
return self.error is None
|
||||
_, _, error = self.run_commands(*command)
|
||||
return error is None
|
||||
|
||||
def addvalues(self, tag, *values):
|
||||
""" Add one or more value(s) to tag
|
||||
@@ -190,6 +192,7 @@ class ExifTool:
|
||||
True if success otherwise False
|
||||
|
||||
If error generated by exiftool, returns False and sets self.error to error string
|
||||
If warning generated by exiftool, returns True (unless there was also an error) and sets self.warning to warning string
|
||||
If called in context manager, returns True (execution is delayed until exiting context manager)
|
||||
|
||||
Notes: exiftool may add duplicate values for some tags so the caller must ensure
|
||||
@@ -216,8 +219,8 @@ class ExifTool:
|
||||
self._commands.extend(command)
|
||||
return True
|
||||
else:
|
||||
_, self.error = self.run_commands(*command)
|
||||
return self.error is None
|
||||
_, _, error = self.run_commands(*command)
|
||||
return error is None
|
||||
|
||||
def run_commands(self, *commands, no_file=False):
|
||||
""" Run commands in the exiftool process and return result.
|
||||
@@ -228,11 +231,12 @@ class ExifTool:
|
||||
by default, all commands will be run against self.file
|
||||
use no_file=True to run a command without passing the filename
|
||||
Returns:
|
||||
(output, errror)
|
||||
(output, warning, errror)
|
||||
output: bytes is containing output of exiftool commands
|
||||
error: if exiftool generated an error, bytes containing error string otherwise None
|
||||
warning: if exiftool generated warnings, string containing warning otherwise empty string
|
||||
error: if exiftool generated errors, string containing otherwise empty string
|
||||
|
||||
Note: Also sets self.error if error generated.
|
||||
Note: Also sets self.warning and self.error if warning or error generated.
|
||||
"""
|
||||
if not (hasattr(self, "_process") and self._process):
|
||||
raise ValueError("exiftool process is not running")
|
||||
@@ -259,16 +263,21 @@ class ExifTool:
|
||||
|
||||
# read the output
|
||||
output = b""
|
||||
warning = b""
|
||||
error = b""
|
||||
while EXIFTOOL_STAYOPEN_EOF not in str(output):
|
||||
line = self._process.stdout.readline()
|
||||
if line.startswith(b"Warning"):
|
||||
error += line
|
||||
warning += line.strip()
|
||||
elif line.startswith(b"Error"):
|
||||
error += line.strip()
|
||||
else:
|
||||
output += line.strip()
|
||||
error = None if error == b"" else error
|
||||
warning = "" if warning == b"" else warning.decode("utf-8")
|
||||
error = "" if error == b"" else error.decode("utf-8")
|
||||
self.warning = warning
|
||||
self.error = error
|
||||
return output[:-EXIFTOOL_STAYOPEN_EOF_LEN], error
|
||||
return output[:-EXIFTOOL_STAYOPEN_EOF_LEN], warning, error
|
||||
|
||||
@property
|
||||
def pid(self):
|
||||
@@ -278,14 +287,14 @@ class ExifTool:
|
||||
@property
|
||||
def version(self):
|
||||
""" returns exiftool version """
|
||||
ver, _ = self.run_commands("-ver", no_file=True)
|
||||
ver, _, _ = self.run_commands("-ver", no_file=True)
|
||||
return ver.decode("utf-8")
|
||||
|
||||
def asdict(self):
|
||||
""" return dictionary of all EXIF tags and values from exiftool
|
||||
returns empty dict if no tags
|
||||
"""
|
||||
json_str, _ = self.run_commands("-json")
|
||||
json_str, _, _ = self.run_commands("-json")
|
||||
if json_str:
|
||||
exifdict = json.loads(json_str)
|
||||
return exifdict[0]
|
||||
@@ -294,7 +303,7 @@ class ExifTool:
|
||||
|
||||
def json(self):
|
||||
""" returns JSON string containing all EXIF tags and values from exiftool """
|
||||
json, _ = self.run_commands("-json")
|
||||
json, _, _ = self.run_commands("-json")
|
||||
return json
|
||||
|
||||
def _read_exif(self):
|
||||
@@ -314,4 +323,5 @@ class ExifTool:
|
||||
if exc_type:
|
||||
return False
|
||||
elif self._commands:
|
||||
_, self.error = self.run_commands(*self._commands)
|
||||
# run_commands sets self.warning and self.error as needed
|
||||
self.run_commands(*self._commands)
|
||||
|
||||
@@ -45,24 +45,105 @@ from ..photokit import (
|
||||
)
|
||||
from ..utils import dd_to_dms_str, findfiles, noop
|
||||
|
||||
ExportResults = namedtuple(
|
||||
"ExportResults",
|
||||
[
|
||||
"exported",
|
||||
"new",
|
||||
"updated",
|
||||
"skipped",
|
||||
"exif_updated",
|
||||
"touched",
|
||||
"converted_to_jpeg",
|
||||
"sidecar_json_written",
|
||||
"sidecar_json_skipped",
|
||||
"sidecar_xmp_written",
|
||||
"sidecar_xmp_skipped",
|
||||
"missing",
|
||||
"error",
|
||||
],
|
||||
)
|
||||
|
||||
class ExportResults:
|
||||
""" holds export results for export2 """
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
exported=None,
|
||||
new=None,
|
||||
updated=None,
|
||||
skipped=None,
|
||||
exif_updated=None,
|
||||
touched=None,
|
||||
converted_to_jpeg=None,
|
||||
sidecar_json_written=None,
|
||||
sidecar_json_skipped=None,
|
||||
sidecar_xmp_written=None,
|
||||
sidecar_xmp_skipped=None,
|
||||
missing=None,
|
||||
error=None,
|
||||
exiftool_warning=None,
|
||||
exiftool_error=None,
|
||||
):
|
||||
self.exported = exported or []
|
||||
self.new = new or []
|
||||
self.updated = updated or []
|
||||
self.skipped = skipped or []
|
||||
self.exif_updated = exif_updated or []
|
||||
self.touched = touched or []
|
||||
self.converted_to_jpeg = converted_to_jpeg or []
|
||||
self.sidecar_json_written = sidecar_json_written or []
|
||||
self.sidecar_json_skipped = sidecar_json_skipped or []
|
||||
self.sidecar_xmp_written = sidecar_xmp_written or []
|
||||
self.sidecar_xmp_skipped = sidecar_xmp_skipped or []
|
||||
self.missing = missing or []
|
||||
self.error = error or []
|
||||
self.exiftool_warning = exiftool_warning or []
|
||||
self.exiftool_error = exiftool_error or []
|
||||
|
||||
def all_files(self):
|
||||
""" return all filenames contained in results """
|
||||
files = (
|
||||
self.exported
|
||||
+ self.new
|
||||
+ self.updated
|
||||
+ self.skipped
|
||||
+ self.exif_updated
|
||||
+ self.touched
|
||||
+ self.converted_to_jpeg
|
||||
+ self.sidecar_json_written
|
||||
+ self.sidecar_json_skipped
|
||||
+ self.sidecar_xmp_written
|
||||
+ self.sidecar_xmp_skipped
|
||||
+ self.missing
|
||||
+ self.error
|
||||
)
|
||||
files += [x[0] for x in self.exiftool_warning]
|
||||
files += [x[0] for x in self.exiftool_error]
|
||||
|
||||
files = list(set(files))
|
||||
return files
|
||||
|
||||
def __iadd__(self, other):
|
||||
self.exported += other.exported
|
||||
self.new += other.new
|
||||
self.updated += other.updated
|
||||
self.skipped += other.skipped
|
||||
self.exif_updated += other.exif_updated
|
||||
self.touched += other.touched
|
||||
self.converted_to_jpeg += other.converted_to_jpeg
|
||||
self.sidecar_json_written += other.sidecar_json_written
|
||||
self.sidecar_json_skipped += other.sidecar_json_skipped
|
||||
self.sidecar_xmp_written += other.sidecar_xmp_written
|
||||
self.sidecar_xmp_skipped += other.sidecar_xmp_skipped
|
||||
self.missing += other.missing
|
||||
self.error += other.error
|
||||
self.exiftool_warning += other.exiftool_warning
|
||||
self.exiftool_error += other.exiftool_error
|
||||
return self
|
||||
|
||||
def __str__(self):
|
||||
return (
|
||||
"ExportResults("
|
||||
+ f"exported={self.exported}"
|
||||
+ f",new={self.new}"
|
||||
+ f",updated={self.updated}"
|
||||
+ f",skipped={self.skipped}"
|
||||
+ f",exif_updated={self.exif_updated}"
|
||||
+ f",touched={self.touched}"
|
||||
+ f",converted_to_jpeg={self.converted_to_jpeg}"
|
||||
+ f",sidecar_json_written={self.sidecar_json_written}"
|
||||
+ f",sidecar_json_skipped={self.sidecar_json_skipped}"
|
||||
+ f",sidecar_xmp_written={self.sidecar_xmp_written}"
|
||||
+ f",sidecar_xmp_skipped={self.sidecar_xmp_skipped}"
|
||||
+ f",missing={self.missing}"
|
||||
+ f",error={self.error}"
|
||||
+ f",exiftool_warning={self.exiftool_warning}"
|
||||
+ f",exiftool_error={self.exiftool_error}"
|
||||
+ ")"
|
||||
)
|
||||
|
||||
|
||||
# hexdigest is not a class method, don't import this into PhotoInfo
|
||||
@@ -389,7 +470,8 @@ def export2(
|
||||
ignore_date_modified: for use with sidecar and exiftool; if True, sets EXIF:ModifyDate to EXIF:DateTimeOriginal even if date_modified is set
|
||||
verbose: optional callable function to use for printing verbose text during processing; if None (default), does not print output.
|
||||
|
||||
Returns: ExportResults namedtuple with fields:
|
||||
Returns: ExportResults class
|
||||
ExportResults has attributes:
|
||||
"exported",
|
||||
"new",
|
||||
"updated",
|
||||
@@ -402,7 +484,10 @@ def export2(
|
||||
"sidecar_xmp_written",
|
||||
"sidecar_xmp_skipped",
|
||||
"missing",
|
||||
"error"
|
||||
"error",
|
||||
"exiftool_warning",
|
||||
"exiftool_error",
|
||||
|
||||
|
||||
Note: to use dry run mode, you must set dry_run=True and also pass in memory version of export_db,
|
||||
and no-op fileutil (e.g. ExportDBInMemory and FileUtilNoOp)
|
||||
@@ -853,6 +938,10 @@ def export2(
|
||||
exif_files = exported_files
|
||||
|
||||
exif_files_updated = []
|
||||
exiftool_warning = []
|
||||
exiftool_error = []
|
||||
errors = []
|
||||
# TODO: remove duplicative code from below
|
||||
if exiftool and update and exif_files:
|
||||
for exported_file in exif_files:
|
||||
files_are_different = False
|
||||
@@ -876,7 +965,7 @@ def export2(
|
||||
# or files were different
|
||||
verbose(f"Writing metadata with exiftool for {exported_file}")
|
||||
if not dry_run:
|
||||
self._write_exif_data(
|
||||
warning_, error_ = self._write_exif_data(
|
||||
exported_file,
|
||||
use_albums_as_keywords=use_albums_as_keywords,
|
||||
use_persons_as_keywords=use_persons_as_keywords,
|
||||
@@ -884,6 +973,12 @@ def export2(
|
||||
description_template=description_template,
|
||||
ignore_date_modified=ignore_date_modified,
|
||||
)
|
||||
if warning_:
|
||||
exiftool_warning.append((exported_file, warning_))
|
||||
if error_:
|
||||
exiftool_error.append((exported_file, error_))
|
||||
errors.append(exported_file)
|
||||
|
||||
export_db.set_exifdata_for_file(
|
||||
exported_file,
|
||||
self._exiftool_json_sidecar(
|
||||
@@ -904,7 +999,7 @@ def export2(
|
||||
for exported_file in exif_files:
|
||||
verbose(f"Writing metadata with exiftool for {exported_file}")
|
||||
if not dry_run:
|
||||
self._write_exif_data(
|
||||
warning_, error_ = self._write_exif_data(
|
||||
exported_file,
|
||||
use_albums_as_keywords=use_albums_as_keywords,
|
||||
use_persons_as_keywords=use_persons_as_keywords,
|
||||
@@ -912,6 +1007,11 @@ def export2(
|
||||
description_template=description_template,
|
||||
ignore_date_modified=ignore_date_modified,
|
||||
)
|
||||
if warning_:
|
||||
exiftool_warning.append((exported_file, warning_))
|
||||
if error_:
|
||||
exiftool_error.append((exported_file, error_))
|
||||
errors.append(exported_file)
|
||||
|
||||
export_db.set_exifdata_for_file(
|
||||
exported_file,
|
||||
@@ -949,8 +1049,9 @@ def export2(
|
||||
sidecar_json_skipped=sidecar_json_files_skipped,
|
||||
sidecar_xmp_written=sidecar_xmp_files_written,
|
||||
sidecar_xmp_skipped=sidecar_xmp_files_skipped,
|
||||
missing=[],
|
||||
error=[],
|
||||
error=errors,
|
||||
exiftool_error=exiftool_error,
|
||||
exiftool_warning=exiftool_warning,
|
||||
)
|
||||
return results
|
||||
|
||||
@@ -1152,6 +1253,9 @@ def _write_exif_data(
|
||||
use_persons_as_keywords: treat person names as keywords
|
||||
keyword_template: (list of strings); list of template strings to render as keywords
|
||||
ignore_date_modified: if True, sets EXIF:ModifyDate to EXIF:DateTimeOriginal even if date_modified is set
|
||||
|
||||
Returns:
|
||||
(warning, error) of warning and error strings if exiftool produces warnings or errors
|
||||
"""
|
||||
if not os.path.exists(filepath):
|
||||
raise FileNotFoundError(f"Could not find file {filepath}")
|
||||
@@ -1170,6 +1274,7 @@ def _write_exif_data(
|
||||
exiftool.setvalue(exiftag, v)
|
||||
else:
|
||||
exiftool.setvalue(exiftag, val)
|
||||
return exiftool.warning, exiftool.error
|
||||
|
||||
|
||||
def _exiftool_dict(
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -7,7 +7,7 @@
|
||||
<key>hostuuid</key>
|
||||
<string>9575E48B-8D5F-5654-ABAC-4431B1167324</string>
|
||||
<key>pid</key>
|
||||
<integer>485</integer>
|
||||
<integer>19275</integer>
|
||||
<key>processname</key>
|
||||
<string>photolibraryd</string>
|
||||
<key>uid</key>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 550 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 171 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
tests/test-images/badimage.jpeg
Normal file
BIN
tests/test-images/badimage.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 500 KiB |
BIN
tests/test-images/screenshot-really-a-png.jpeg
Normal file
BIN
tests/test-images/screenshot-really-a-png.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 550 KiB |
@@ -18,10 +18,10 @@ PHOTOS_DB = "tests/Test-10.15.7.photoslibrary/database/photos.db"
|
||||
PHOTOS_DB_PATH = "/Test-10.15.7.photoslibrary/database/photos.db"
|
||||
PHOTOS_LIBRARY_PATH = "/Test-10.15.7.photoslibrary"
|
||||
|
||||
PHOTOS_DB_LEN = 18
|
||||
PHOTOS_NOT_IN_TRASH_LEN = 16
|
||||
PHOTOS_DB_LEN = 19
|
||||
PHOTOS_NOT_IN_TRASH_LEN = 17
|
||||
PHOTOS_IN_TRASH_LEN = 2
|
||||
PHOTOS_DB_IMPORT_SESSIONS = 13
|
||||
PHOTOS_DB_IMPORT_SESSIONS = 14
|
||||
|
||||
KEYWORDS = [
|
||||
"Kids",
|
||||
@@ -971,7 +971,7 @@ def test_from_to_date(photosdb):
|
||||
time.tzset()
|
||||
|
||||
photos = photosdb.photos(from_date=datetime.datetime(2018, 10, 28))
|
||||
assert len(photos) == 9
|
||||
assert len(photos) == 10
|
||||
|
||||
photos = photosdb.photos(to_date=datetime.datetime(2018, 10, 28))
|
||||
assert len(photos) == 7
|
||||
|
||||
@@ -405,6 +405,8 @@ CLI_EXIFTOOL_IGNORE_DATE_MODIFIED = {
|
||||
}
|
||||
}
|
||||
|
||||
CLI_EXIFTOOL_ERROR = ["E2078879-A29C-4D6F-BACB-E3BBE6C3EB91"]
|
||||
|
||||
LABELS_JSON = {
|
||||
"labels": {
|
||||
"Plant": 7,
|
||||
@@ -1091,6 +1093,34 @@ def test_export_exiftool_quicktime():
|
||||
for filename in files:
|
||||
os.unlink(filename)
|
||||
|
||||
@pytest.mark.skipif(exiftool is None, reason="exiftool not installed")
|
||||
def test_export_exiftool_error():
|
||||
"""" test --exiftool catching error """
|
||||
import glob
|
||||
import os
|
||||
import os.path
|
||||
from osxphotos.__main__ import export
|
||||
from osxphotos.exiftool import ExifTool
|
||||
|
||||
runner = CliRunner()
|
||||
cwd = os.getcwd()
|
||||
# pylint: disable=not-context-manager
|
||||
with runner.isolated_filesystem():
|
||||
for uuid in CLI_EXIFTOOL_ERROR:
|
||||
result = runner.invoke(
|
||||
export,
|
||||
[
|
||||
os.path.join(cwd, PHOTOS_DB_15_7),
|
||||
".",
|
||||
"-V",
|
||||
"--exiftool",
|
||||
"--uuid",
|
||||
f"{uuid}",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "exiftool error" in result.output
|
||||
|
||||
|
||||
def test_export_edited_suffix():
|
||||
""" test export with --edited-suffix """
|
||||
|
||||
@@ -2,6 +2,7 @@ import pytest
|
||||
from osxphotos.exiftool import get_exiftool_path
|
||||
|
||||
TEST_FILE_ONE_KEYWORD = "tests/test-images/wedding.jpg"
|
||||
TEST_FILE_BAD_IMAGE = "tests/test-images/badimage.jpeg"
|
||||
TEST_FILE_MULTI_KEYWORD = "tests/test-images/Tulips.jpg"
|
||||
TEST_MULTI_KEYWORDS = [
|
||||
"Top Shot",
|
||||
@@ -109,8 +110,8 @@ def test_setvalue_1():
|
||||
assert exif.data["IPTC:Keywords"] == "test"
|
||||
|
||||
|
||||
def test_setvalue_error():
|
||||
# test setting illegal tag value generates error
|
||||
def test_setvalue_warning():
|
||||
# test setting illegal tag value generates warning
|
||||
import os.path
|
||||
import tempfile
|
||||
import osxphotos.exiftool
|
||||
@@ -122,6 +123,22 @@ def test_setvalue_error():
|
||||
|
||||
exif = osxphotos.exiftool.ExifTool(tempfile)
|
||||
exif.setvalue("IPTC:Foo", "test")
|
||||
assert exif.warning
|
||||
|
||||
|
||||
def test_setvalue_error():
|
||||
# test setting tag on bad image generates error
|
||||
import os.path
|
||||
import tempfile
|
||||
import osxphotos.exiftool
|
||||
from osxphotos.fileutil import FileUtil
|
||||
|
||||
tempdir = tempfile.TemporaryDirectory(prefix="osxphotos_")
|
||||
tempfile = os.path.join(tempdir.name, os.path.basename(TEST_FILE_BAD_IMAGE))
|
||||
FileUtil.copy(TEST_FILE_BAD_IMAGE, tempfile)
|
||||
|
||||
exif = osxphotos.exiftool.ExifTool(tempfile)
|
||||
exif.setvalue("IPTC:Keywords", "test")
|
||||
assert exif.error
|
||||
|
||||
|
||||
@@ -142,7 +159,7 @@ def test_setvalue_context_manager():
|
||||
exif.setvalue("XMP:Title", "title")
|
||||
exif.setvalue("XMP:Subject", "subject")
|
||||
|
||||
assert exif.error is None
|
||||
assert not exif.error
|
||||
|
||||
exif2 = osxphotos.exiftool.ExifTool(tempfile)
|
||||
exif2._read_exif()
|
||||
@@ -151,8 +168,8 @@ def test_setvalue_context_manager():
|
||||
assert exif2.data["XMP:Subject"] == "subject"
|
||||
|
||||
|
||||
def test_setvalue_context_manager_error():
|
||||
# test setting a tag value as context manager when error generated
|
||||
def test_setvalue_context_manager_warning():
|
||||
# test setting a tag value as context manager when warning generated
|
||||
import os.path
|
||||
import tempfile
|
||||
import osxphotos.exiftool
|
||||
@@ -164,6 +181,22 @@ def test_setvalue_context_manager_error():
|
||||
|
||||
with osxphotos.exiftool.ExifTool(tempfile) as exif:
|
||||
exif.setvalue("Foo:Bar", "test1")
|
||||
assert exif.warning
|
||||
|
||||
|
||||
def test_setvalue_context_manager_error():
|
||||
# test setting a tag value as context manager when error generated
|
||||
import os.path
|
||||
import tempfile
|
||||
import osxphotos.exiftool
|
||||
from osxphotos.fileutil import FileUtil
|
||||
|
||||
tempdir = tempfile.TemporaryDirectory(prefix="osxphotos_")
|
||||
tempfile = os.path.join(tempdir.name, os.path.basename(TEST_FILE_BAD_IMAGE))
|
||||
FileUtil.copy(TEST_FILE_BAD_IMAGE, tempfile)
|
||||
|
||||
with osxphotos.exiftool.ExifTool(tempfile) as exif:
|
||||
exif.setvalue("IPTC:Keywords", "test1")
|
||||
assert exif.error
|
||||
|
||||
|
||||
|
||||
@@ -716,7 +716,7 @@ def test_xmp_sidecar_is_valid(tmp_path):
|
||||
xmp_file = tmp_path / XMP_FILENAME
|
||||
assert xmp_file.is_file()
|
||||
exiftool = ExifTool(str(xmp_file))
|
||||
output, _ = exiftool.run_commands("-validate", "-warning")
|
||||
output, _, _ = exiftool.run_commands("-validate", "-warning")
|
||||
assert output == b"[ExifTool] Validate : 0 0 0"
|
||||
|
||||
|
||||
|
||||
95
tests/test_exportresults.py
Normal file
95
tests/test_exportresults.py
Normal file
@@ -0,0 +1,95 @@
|
||||
""" test ExportResults class """
|
||||
|
||||
import pytest
|
||||
from osxphotos.photoinfo import ExportResults
|
||||
|
||||
EXPORT_RESULT_ATTRIBUTES = [
|
||||
"exported",
|
||||
"new",
|
||||
"updated",
|
||||
"skipped",
|
||||
"exif_updated",
|
||||
"touched",
|
||||
"converted_to_jpeg",
|
||||
"sidecar_json_written",
|
||||
"sidecar_json_skipped",
|
||||
"sidecar_xmp_written",
|
||||
"sidecar_xmp_skipped",
|
||||
"missing",
|
||||
"error",
|
||||
"exiftool_warning",
|
||||
"exiftool_error",
|
||||
]
|
||||
|
||||
|
||||
def test_exportresults_init():
|
||||
results = ExportResults()
|
||||
assert results.exported == []
|
||||
assert results.new == []
|
||||
assert results.updated == []
|
||||
assert results.skipped == []
|
||||
assert results.exif_updated == []
|
||||
assert results.touched == []
|
||||
assert results.converted_to_jpeg == []
|
||||
assert results.sidecar_json_written == []
|
||||
assert results.sidecar_json_skipped == []
|
||||
assert results.sidecar_xmp_written == []
|
||||
assert results.sidecar_xmp_skipped == []
|
||||
assert results.missing == []
|
||||
assert results.error == []
|
||||
assert results.exiftool_warning == []
|
||||
assert results.exiftool_error == []
|
||||
|
||||
|
||||
def test_exportresults_iadd():
|
||||
results1 = ExportResults()
|
||||
results2 = ExportResults()
|
||||
for x in EXPORT_RESULT_ATTRIBUTES:
|
||||
setattr(results1, x, [f"{x}1"])
|
||||
setattr(results2, x, [f"{x}2"])
|
||||
|
||||
results1 += results2
|
||||
for x in EXPORT_RESULT_ATTRIBUTES:
|
||||
assert getattr(results1, x) == [f"{x}1", f"{x}2"]
|
||||
|
||||
# exiftool_warning and exiftool_error are lists of tuples
|
||||
results1 = ExportResults()
|
||||
results2 = ExportResults()
|
||||
results1.exiftool_warning = [("exiftool_warning1", "foo")]
|
||||
results2.exiftool_warning = [("exiftool_warning2", "bar")]
|
||||
results1.exiftool_error = [("exiftool_error1", "foo")]
|
||||
results2.exiftool_error = [("exiftool_error2", "bar")]
|
||||
|
||||
results1 += results2
|
||||
|
||||
assert results1.exiftool_warning == [
|
||||
("exiftool_warning1", "foo"),
|
||||
("exiftool_warning2", "bar"),
|
||||
]
|
||||
assert results1.exiftool_error == [
|
||||
("exiftool_error1", "foo"),
|
||||
("exiftool_error2", "bar"),
|
||||
]
|
||||
|
||||
|
||||
def test_all_files():
|
||||
""" test ExportResults.all_files() """
|
||||
results = ExportResults()
|
||||
for x in EXPORT_RESULT_ATTRIBUTES:
|
||||
setattr(results, x, [f"{x}1"])
|
||||
results.exiftool_warning = [("exiftool_warning1", "foo")]
|
||||
results.exiftool_error = [("exiftool_error1", "foo")]
|
||||
|
||||
assert sorted(results.all_files()) == sorted(
|
||||
[f"{x}1" for x in EXPORT_RESULT_ATTRIBUTES]
|
||||
)
|
||||
|
||||
|
||||
def test_str():
|
||||
""" test ExportResults.__str__ """
|
||||
results = ExportResults()
|
||||
assert (
|
||||
str(results)
|
||||
== "ExportResults(exported=[],new=[],updated=[],skipped=[],exif_updated=[],touched=[],converted_to_jpeg=[],sidecar_json_written=[],sidecar_json_skipped=[],sidecar_xmp_written=[],sidecar_xmp_skipped=[],missing=[],error=[],exiftool_warning=[],exiftool_error=[])"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user