From 4d924d08269e009b0bfd818f9daafba4ccad98c3 Mon Sep 17 00:00:00 2001 From: Rhet Turnbull Date: Mon, 11 Jan 2021 06:45:35 -0800 Subject: [PATCH] Completed implementation of --jpeg-ext, fixed --dry-run, closes #330, #346 --- osxphotos/_version.py | 2 +- osxphotos/fileutil.py | 24 +++++++ osxphotos/photoinfo/_photoinfo_export.py | 84 ++++++++++++++++++------ tests/test_fileutil.py | 18 +++++ 4 files changed, 108 insertions(+), 20 deletions(-) diff --git a/osxphotos/_version.py b/osxphotos/_version.py index bca13460..5133460d 100644 --- a/osxphotos/_version.py +++ b/osxphotos/_version.py @@ -1,3 +1,3 @@ """ version info """ -__version__ = "0.39.14" +__version__ = "0.39.15" diff --git a/osxphotos/fileutil.py b/osxphotos/fileutil.py index 9d31321b..b3518bca 100644 --- a/osxphotos/fileutil.py +++ b/osxphotos/fileutil.py @@ -60,6 +60,11 @@ class FileUtilABC(ABC): def convert_to_jpeg(cls, src_file, dest_file, compression_quality=1.0): pass + @classmethod + @abstractmethod + def rename(cls, src, dest): + pass + class FileUtilMacOS(FileUtilABC): """ Various file utilities """ @@ -201,6 +206,21 @@ class FileUtilMacOS(FileUtilABC): src_file, dest_file, compression_quality=compression_quality ) + @classmethod + def rename(cls, src, dest): + """ Copy src to dest + + Args: + src: path to source file + dest: path to destination file + + Returns: + Name of renamed file (dest) + + """ + os.rename(str(src), str(dest)) + return dest + @staticmethod def _sig(st): """ return tuple of (mode, size, mtime) of file based on os.stat @@ -266,3 +286,7 @@ class FileUtilNoOp(FileUtil): @classmethod def convert_to_jpeg(cls, src_file, dest_file, compression_quality=1.0): cls.verbose(f"convert_to_jpeg: {src_file}, {dest_file}, {compression_quality}") + + @classmethod + def rename(cls, src, dest): + cls.verbose(f"rename: {src}, {dest}") diff --git a/osxphotos/photoinfo/_photoinfo_export.py b/osxphotos/photoinfo/_photoinfo_export.py index 60bcd1d2..12f46775 100644 --- a/osxphotos/photoinfo/_photoinfo_export.py +++ b/osxphotos/photoinfo/_photoinfo_export.py @@ -49,7 +49,7 @@ from ..photokit import ( PhotoKitFetchFailed, PhotoLibrary, ) -from ..utils import dd_to_dms_str, findfiles, noop +from ..utils import dd_to_dms_str, findfiles, noop, get_preferred_uti_extension class ExportError(Exception): @@ -311,6 +311,34 @@ def _check_export_suffix(src, dest, edited): ) +# not a class method, don't import into PhotoInfo +def rename_jpeg_files(files, jpeg_ext, fileutil): + """ rename any jpeg files in files so that extension matches jpeg_ext + + Args: + files: list of file paths + jpeg_ext: extension to use for jpeg files found in files, e.g. "jpg" + fileutil: a FileUtil object + + Returns: + list of files with updated names + + Note: If non-jpeg files found, they will be ignore and returned in the return list + """ + jpeg_ext = "." + jpeg_ext + jpegs = [".jpeg", ".jpg"] + new_files = [] + for file in files: + path = pathlib.Path(file) + if path.suffix.lower() in jpegs and path.suffix != jpeg_ext: + new_file = path.parent / (path.stem + jpeg_ext) + fileutil.rename(file, new_file) + new_files.append(new_file) + else: + new_files.append(file) + return new_files + + def export( self, dest, @@ -749,6 +777,8 @@ def export2( ) all_results += results else: + # TODO: move this big if/else block to separate functions + # e.g. _export_with_photos_export or such # use_photo_export # export live_photo .mov file? live_photo = True if live_photo and self.live_photo else False @@ -763,7 +793,10 @@ def export2( else: # didn't get passed a filename, add _edited filestem = f"{dest.stem}{edited_identifier}" - dest = dest.parent / f"{filestem}.jpeg" + uti = self.uti_edited if edited and self.uti_edited else self.uti + ext = get_preferred_uti_extension(uti) + dest = dest.parent / f"{filestem}{ext}" + if use_photokit: photolib = PhotoLibrary() photo = None @@ -786,13 +819,17 @@ def export2( ) ) if photo: - try: - exported = photo.export( - dest.parent, dest.name, version=PHOTOS_VERSION_CURRENT - ) - all_results.exported.extend(exported) - except Exception as e: - all_results.error.append((str(dest), e)) + if not dry_run: + try: + exported = photo.export( + dest.parent, dest.name, version=PHOTOS_VERSION_CURRENT + ) + all_results.exported.extend(exported) + except Exception as e: + all_results.error.append((str(dest), e)) + else: + # dry_run, don't actually export + all_results.exported.append(str(dest)) else: try: exported = _export_photo_uuid_applescript( @@ -827,13 +864,17 @@ def export2( photo = [p for p in bursts if p.uuid.startswith(self.uuid)] photo = photo[0] if photo else None if photo: - try: - exported = photo.export( - dest.parent, dest.name, version=PHOTOS_VERSION_ORIGINAL - ) - all_results.exported.extend(exported) - except Exception as e: - all_results.error.append((str(dest), e)) + if not dry_run: + try: + exported = photo.export( + dest.parent, dest.name, version=PHOTOS_VERSION_ORIGINAL + ) + all_results.exported.extend(exported) + except Exception as e: + all_results.error.append((str(dest), e)) + else: + # dry_run, don't actually export + all_results.exported.append(str(dest)) else: try: exported = _export_photo_uuid_applescript( @@ -851,6 +892,13 @@ def export2( except ExportError as e: all_results.error.append((str(dest), e)) if all_results.exported: + if jpeg_ext: + # use_photos_export (both PhotoKit and AppleScript) don't use the + # file extension provided (instead they use extension for UTI) + # so if jpeg_ext is set, rename any non-conforming jpegs + all_results.exported = rename_jpeg_files( + all_results.exported, jpeg_ext, fileutil + ) if touch_file: for exported_file in all_results.exported: all_results.touched.append(exported_file) @@ -859,9 +907,6 @@ def export2( if update: all_results.new.extend(all_results.exported) - # else: - # all_results.error.append((str(dest), f"Error exporting photo {self.uuid} to {dest} with use_photos_export")) - # export metadata sidecars = [] sidecar_json_files_skipped = [] @@ -1769,3 +1814,4 @@ def _write_sidecar(self, filename, sidecar_str): f = open(filename, "w") f.write(sidecar_str) f.close() + diff --git a/tests/test_fileutil.py b/tests/test_fileutil.py index c52978a8..955a7249 100644 --- a/tests/test_fileutil.py +++ b/tests/test_fileutil.py @@ -107,3 +107,21 @@ def test_convert_to_jpeg_quality(): assert FileUtil.convert_to_jpeg(imgfile, outfile, compression_quality=0.1) assert outfile.is_file() assert outfile.stat().st_size < 1000000 + + +def test_rename_file(): + # rename file with valid src, dest + import pathlib + import tempfile + from osxphotos.fileutil import FileUtil + + temp_dir = tempfile.TemporaryDirectory(prefix="osxphotos_") + src = "tests/test-images/wedding.jpg" + dest = f"{temp_dir.name}/foo.jpg" + dest2 = f"{temp_dir.name}/bar.jpg" + FileUtil.copy(src, dest) + result = FileUtil.rename(dest, dest2) + assert result + assert pathlib.Path(dest2).exists() + assert not pathlib.Path(dest).exists() +