diff --git a/osxphotos/__main__.py b/osxphotos/__main__.py index 8a609305..73714807 100644 --- a/osxphotos/__main__.py +++ b/osxphotos/__main__.py @@ -1543,6 +1543,7 @@ def export( results_updated = [] results_skipped = [] results_exif_updated = [] + results_touched = [] if verbose_: for p in photos: results = export_photo( @@ -1578,6 +1579,7 @@ def export( results_updated.extend(results.updated) results_skipped.extend(results.skipped) results_exif_updated.extend(results.exif_updated) + results_touched.extend(results.touched) else: # show progress bar @@ -1616,24 +1618,31 @@ def export( results_updated.extend(results.updated) results_skipped.extend(results.skipped) results_exif_updated.extend(results.exif_updated) + results_touched.extend(results.touched) stop_time = time.perf_counter() # print summary results if update: photo_str_new = "photos" if len(results_new) != 1 else "photo" photo_str_updated = "photos" if len(results_new) != 1 else "photo" photo_str_skipped = "photos" if len(results_skipped) != 1 else "photo" + photo_str_touched = "photos" if len(results_touched) != 1 else "photo" photo_str_exif_updated = ( "photos" if len(results_exif_updated) != 1 else "photo" ) - click.echo( - f"Exported: {len(results_new)} {photo_str_new}, " - + f"updated: {len(results_updated)} {photo_str_updated}, " - + f"skipped: {len(results_skipped)} {photo_str_skipped}, " - + f"updated EXIF data: {len(results_exif_updated)} {photo_str_exif_updated}" - ) + summary = f"Exported: {len(results_new)} {photo_str_new}, " \ + f"updated: {len(results_updated)} {photo_str_updated}, " \ + f"skipped: {len(results_skipped)} {photo_str_skipped}, " \ + f"updated EXIF data: {len(results_exif_updated)} {photo_str_exif_updated}" + if touch_file: + summary += f", touched date: {len(results_touched)} {photo_str_touched}" + click.echo(summary) else: photo_str = "photos" if len(results_exported) != 1 else "photo" - click.echo(f"Exported: {len(results_exported)} {photo_str}") + photo_str_touched = "photos" if len(results_touched) != 1 else "photo" + summary = f"Exported: {len(results_exported)} {photo_str}" + if touch_file: + summary += f", touched date: {len(results_touched)} {photo_str_touched}" + click.echo(summary) click.echo(f"Elapsed time: {(stop_time-start_time):.3f} seconds") else: click.echo("Did not find any photos to export") @@ -2125,25 +2134,26 @@ def export_photo( if photo.ismissing: space = " " if not verbose_ else "" verbose(f"{space}Skipping missing photo {photo.filename}") - return ExportResults([], [], [], [], []) + return ExportResults([], [], [], [], [], []) elif not os.path.exists(photo.path): space = " " if not verbose_ else "" verbose( f"{space}WARNING: file {photo.path} is missing but ismissing=False, " f"skipping {photo.filename}" ) - return ExportResults([], [], [], [], []) + return ExportResults([], [], [], [], [], []) elif photo.ismissing and not photo.iscloudasset or not photo.incloud: verbose( f"Skipping missing {photo.filename}: not iCloud asset or missing from cloud" ) - return ExportResults([], [], [], [], []) + return ExportResults([], [], [], [], [], []) results_exported = [] results_new = [] results_updated = [] results_skipped = [] results_exif_updated = [] + results_touched = [] filenames = get_filenames_from_template(photo, filename_template, original_name) for filename in filenames: @@ -2196,6 +2206,7 @@ def export_photo( 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) if verbose_: for exported in export_results.exported: @@ -2253,6 +2264,7 @@ def export_photo( 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.touched) if verbose_: for exported in export_results_edited.exported: @@ -2270,6 +2282,7 @@ def export_photo( results_updated, results_skipped, results_exif_updated, + results_touched ) diff --git a/osxphotos/photoinfo/_photoinfo_export.py b/osxphotos/photoinfo/_photoinfo_export.py index 875c462f..33bd3a47 100644 --- a/osxphotos/photoinfo/_photoinfo_export.py +++ b/osxphotos/photoinfo/_photoinfo_export.py @@ -36,7 +36,8 @@ from ..fileutil import FileUtil from ..utils import dd_to_dms_str, findfiles ExportResults = namedtuple( - "ExportResults", ["exported", "new", "updated", "skipped", "exif_updated"] + "ExportResults", + ["exported", "new", "updated", "skipped", "exif_updated", "touched"], ) @@ -376,6 +377,9 @@ def export2( # list of all files skipped because they do not need to be updated (for use with update=True) update_skipped_files = [] + # list of all files with utime touched (touch_file = True) + touched_files = [] + # check edited and raise exception trying to export edited version of # photo that hasn't been edited if edited and not self.hasadjustments: @@ -566,6 +570,7 @@ def export2( update_new_files = results.new update_updated_files = results.updated update_skipped_files = results.skipped + touched_files = results.touched # copy live photo associated .mov if requested if live_photo and self.live_photo: @@ -592,6 +597,7 @@ def export2( update_new_files.extend(results.new) update_updated_files.extend(results.updated) update_skipped_files.extend(results.skipped) + touched_files.extend(results.touched) else: logging.debug(f"Skipping missing live movie for {filename}") @@ -618,6 +624,7 @@ def export2( update_new_files.extend(results.new) update_updated_files.extend(results.updated) update_skipped_files.extend(results.skipped) + touched_files.extend(results.touched) else: logging.debug(f"Skipping missing RAW photo for {filename}") else: @@ -662,6 +669,7 @@ def export2( ) if exported is not None: + # TODO: add touch_file code exported_files.extend(exported) else: logging.warning( @@ -704,6 +712,7 @@ def export2( raise e # if exiftool, write the metadata + # TODO: add touch_file code to update touch time if update: exif_files = update_new_files + update_updated_files + update_skipped_files else: @@ -784,6 +793,7 @@ def export2( update_updated_files, update_skipped_files, exif_files_updated, + touched_files, ) @@ -826,6 +836,7 @@ def _export_photo( update_updated_files = [] update_new_files = [] update_skipped_files = [] + touched_files = [] dest_str = str(dest) dest_exists = dest.exists() @@ -838,11 +849,15 @@ def _export_photo( # not update, export the file logging.debug(f"Exporting file with {op_desc} {src} {dest}") exported_files.append(dest_str) + if touch_file: + touched_files.append(dest_str) else: # updating if not dest_exists: # update, destination doesn't exist (new file) logging.debug(f"Update: exporting new file with {op_desc} {src} {dest}") update_new_files.append(dest_str) + if touch_file: + touched_files.append(dest_str) else: # update, destination exists, but we might not need to replace it... if touch_file: @@ -863,33 +878,38 @@ def _export_photo( ) ): # destination exists but its signature is "identical" - logging.debug(f"Update: skipping identical original files {src} {dest}") # call set_stat because code can reach this spot if no export DB but exporting a RAW or live photo # potentially re-writes the data in the database but ensures database is complete export_db.set_stat_orig_for_file(dest_str, fileutil.file_sig(dest_str)) update_skipped_files.append(dest_str) else: - # destination exists but is different - logging.debug( - f"Update: removing existing file prior to {op_desc} {src} {dest}" - ) - update_updated_files.append(dest_str) + # destination exists but signature is different + if touch_file and fileutil.cmp(src, dest) and not sig_cmp: + # destination exists, signature matches original but does not match expected touch time + # skip exporting but update touch time + update_skipped_files.append(dest_str) + touched_files.append(dest_str) + else: + # destination exists but is different + update_updated_files.append(dest_str) + if touch_file: + touched_files.append(dest_str) if not update_skipped_files: if dest_exists and (update or overwrite): # need to remove the destination first logging.debug( - f"Update: removing existing file prior to export_as_hardlink {src} {dest}" + f"Update: removing existing file prior to {op_desc} {src} {dest}" ) - # dest.unlink() fileutil.unlink(dest) if export_as_hardlink: fileutil.hardlink(src, dest) else: fileutil.copy(src, dest_str, norsrc=no_xattr) - if touch_file: - ts = self.date.timestamp() - fileutil.utime(dest, (ts, ts)) + + if touched_files: + ts = self.date.timestamp() + fileutil.utime(dest, (ts, ts)) export_db.set_data( dest_str, @@ -906,6 +926,7 @@ def _export_photo( update_updated_files, update_skipped_files, [], + touched_files, )