diff --git a/README.md b/README.md index 59c432c2..e24059bf 100644 --- a/README.md +++ b/README.md @@ -1704,7 +1704,7 @@ Substitution Description {lf} A line feed: '\n', alias for {newline} {cr} A carriage return: '\r' {crlf} a carriage return + line feed: '\r\n' -{osxphotos_version} The osxphotos version, e.g. '0.43.3' +{osxphotos_version} The osxphotos version, e.g. '0.43.4' {osxphotos_cmd_line} The full command line used to run osxphotos The following substitutions may result in multiple values. Thus if specified for @@ -3574,7 +3574,7 @@ The following template field substitutions are availabe for use the templating s |{lf}|A line feed: '\n', alias for {newline}| |{cr}|A carriage return: '\r'| |{crlf}|a carriage return + line feed: '\r\n'| -|{osxphotos_version}|The osxphotos version, e.g. '0.43.3'| +|{osxphotos_version}|The osxphotos version, e.g. '0.43.4'| |{osxphotos_cmd_line}|The full command line used to run osxphotos| |{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| diff --git a/docs/.buildinfo b/docs/.buildinfo index 27af843b..13cd2650 100644 --- a/docs/.buildinfo +++ b/docs/.buildinfo @@ -1,4 +1,4 @@ # 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. -config: c7ae18f496751028df3c1bcd1ca5245c +config: 7a3415c9b6b46da1269550f16ddeb35c tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_modules/index.html b/docs/_modules/index.html index a688b844..1d8d17cf 100644 --- a/docs/_modules/index.html +++ b/docs/_modules/index.html @@ -5,7 +5,7 @@ - Overview: module code — osxphotos 0.43.3 documentation + Overview: module code — osxphotos 0.43.4 documentation diff --git a/docs/_modules/osxphotos/photoinfo/_photoinfo_export.html b/docs/_modules/osxphotos/photoinfo/_photoinfo_export.html index 0a196bd5..f19c934e 100644 --- a/docs/_modules/osxphotos/photoinfo/_photoinfo_export.html +++ b/docs/_modules/osxphotos/photoinfo/_photoinfo_export.html @@ -5,7 +5,7 @@ - osxphotos.photoinfo._photoinfo_export — osxphotos 0.42.94 documentation + osxphotos.photoinfo._photoinfo_export — osxphotos 0.43.4 documentation @@ -1299,6 +1299,7 @@ dest.name, version=PHOTOS_VERSION_CURRENT, overwrite=overwrite, + video=live_photo, ) all_results.exported.extend(exported) except Exception as e: @@ -1346,6 +1347,7 @@ dest.name, version=PHOTOS_VERSION_ORIGINAL, overwrite=overwrite, + video=live_photo, ) all_results.exported.extend(exported) except Exception as e: diff --git a/docs/_static/documentation_options.js b/docs/_static/documentation_options.js index 56494323..cfc330f7 100644 --- a/docs/_static/documentation_options.js +++ b/docs/_static/documentation_options.js @@ -1,6 +1,6 @@ var DOCUMENTATION_OPTIONS = { URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '0.43.3', + VERSION: '0.43.4', LANGUAGE: 'None', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/docs/cli.html b/docs/cli.html index 7d7ace33..aa9569be 100644 --- a/docs/cli.html +++ b/docs/cli.html @@ -5,7 +5,7 @@ - osxphotos command line interface (CLI) — osxphotos 0.43.3 documentation + osxphotos command line interface (CLI) — osxphotos 0.43.4 documentation diff --git a/docs/genindex.html b/docs/genindex.html index 0a52d3cf..f2aa7ce7 100644 --- a/docs/genindex.html +++ b/docs/genindex.html @@ -5,7 +5,7 @@ - Index — osxphotos 0.43.3 documentation + Index — osxphotos 0.43.4 documentation diff --git a/docs/index.html b/docs/index.html index d892352c..3bf66f7a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -5,7 +5,7 @@ - Welcome to osxphotos’s documentation! — osxphotos 0.43.3 documentation + Welcome to osxphotos’s documentation! — osxphotos 0.43.4 documentation diff --git a/docs/modules.html b/docs/modules.html index 8a18386e..9b5ecf27 100644 --- a/docs/modules.html +++ b/docs/modules.html @@ -5,7 +5,7 @@ - osxphotos — osxphotos 0.43.3 documentation + osxphotos — osxphotos 0.43.4 documentation diff --git a/docs/reference.html b/docs/reference.html index 008ca800..ee14e863 100644 --- a/docs/reference.html +++ b/docs/reference.html @@ -5,7 +5,7 @@ - osxphotos package — osxphotos 0.43.3 documentation + osxphotos package — osxphotos 0.43.4 documentation diff --git a/docs/search.html b/docs/search.html index 613b4933..13a8635a 100644 --- a/docs/search.html +++ b/docs/search.html @@ -5,7 +5,7 @@ - Search — osxphotos 0.43.3 documentation + Search — osxphotos 0.43.4 documentation diff --git a/osxphotos/_version.py b/osxphotos/_version.py index 5608dbbb..881d4c0f 100644 --- a/osxphotos/_version.py +++ b/osxphotos/_version.py @@ -1,3 +1,3 @@ """ version info """ -__version__ = "0.43.3" +__version__ = "0.43.4" diff --git a/osxphotos/photoinfo/_photoinfo_export.py b/osxphotos/photoinfo/_photoinfo_export.py index 191a9955..f836fae7 100644 --- a/osxphotos/photoinfo/_photoinfo_export.py +++ b/osxphotos/photoinfo/_photoinfo_export.py @@ -1266,6 +1266,7 @@ def _export_photo_with_photos_export( dest.name, version=PHOTOS_VERSION_CURRENT, overwrite=overwrite, + video=live_photo, ) all_results.exported.extend(exported) except Exception as e: @@ -1313,6 +1314,7 @@ def _export_photo_with_photos_export( dest.name, version=PHOTOS_VERSION_ORIGINAL, overwrite=overwrite, + video=live_photo, ) all_results.exported.extend(exported) except Exception as e: diff --git a/osxphotos/photokit.py b/osxphotos/photokit.py index 9da8990c..157bb70f 100644 --- a/osxphotos/photokit.py +++ b/osxphotos/photokit.py @@ -6,8 +6,6 @@ """ # NOTES: -# - This likely leaks memory like a sieve as I need to ensure all the -# Objective C objects are cleaned up. # - There are several techniques used for handling PhotoKit's various # asynchronous calls used in this code: event loop+notification, threading # event, while loop. I've experimented with each to find the one that works. @@ -200,16 +198,6 @@ class PHAssetResourceData: self.data = b"" -# class LivePhotoData: -# """ Simple class to hold the data passed to the handler for -# requestLivePhotoForAsset:targetSize:contentMode:options:resultHandler: -# """ - -# def __init__(self): -# self.live_photo = None -# self.info = None - - class PhotoKitNotificationDelegate(NSObject): """Handles notifications from NotificationCenter; used with asynchronous PhotoKit requests to stop event loop when complete @@ -487,6 +475,7 @@ class PhotoAsset: version=PHOTOS_VERSION_CURRENT, overwrite=False, raw=False, + **kwargs, ): """Export image to path @@ -496,6 +485,7 @@ class PhotoAsset: version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT) overwrite: bool, if True, overwrites destination file if it already exists; default is False raw: bool, if True, export RAW component of RAW+JPEG pair, default is False + **kwargs: used only to avoid issues with each asset type having slightly different export arguments Returns: List of path to exported image(s) @@ -504,9 +494,6 @@ class PhotoAsset: ValueError if dest is not a valid directory """ - # if self.live: - # raise NotImplementedError("Live photos not implemented yet") - with objc.autorelease_pool(): filename = ( pathlib.Path(filename) @@ -615,9 +602,7 @@ class PhotoAsset: nonlocal requestdata - options = {} - # pylint: disable=no-member - options[Quartz.kCGImageSourceShouldCache] = Foundation.kCFBooleanFalse + options = {Quartz.kCGImageSourceShouldCache: Foundation.kCFBooleanFalse} imgSrc = Quartz.CGImageSourceCreateWithData(imageData, options) requestdata.metadata = Quartz.CGImageSourceCopyPropertiesAtIndex( imgSrc, 0, options @@ -701,9 +686,7 @@ class PhotoAsset: nonlocal data - options = {} - # pylint: disable=no-member - options[Quartz.kCGImageSourceShouldCache] = Foundation.kCFBooleanFalse + options = {Quartz.kCGImageSourceShouldCache: Foundation.kCFBooleanFalse} imgSrc = Quartz.CGImageSourceCreateWithData(imageData, options) data.metadata = Quartz.CGImageSourceCopyPropertiesAtIndex( imgSrc, 0, options @@ -789,7 +772,6 @@ class SlowMoVideoExporter(NSObject): self.url = None self.done = None self.nc = None - # super(NSObject, self).dealloc() class VideoAsset(PhotoAsset): @@ -801,7 +783,12 @@ class VideoAsset(PhotoAsset): # https://developer.apple.com/documentation/photokit/phimagemanager/1616981-requestexportsessionforvideo?language=objc # above 10.15 only def export( - self, dest, filename=None, version=PHOTOS_VERSION_CURRENT, overwrite=False + self, + dest, + filename=None, + version=PHOTOS_VERSION_CURRENT, + overwrite=False, + **kwargs, ): """Export video to path @@ -810,6 +797,7 @@ class VideoAsset(PhotoAsset): filename: str, optional name of exported file; if not provided, defaults to asset's original filename version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT) overwrite: bool, if True, overwrites destination file if it already exists; default is False + **kwargs: used only to avoid issues with each asset type having slightly different export arguments Returns: List of path to exported image(s) @@ -1043,6 +1031,7 @@ class LivePhotoAsset(PhotoAsset): overwrite=False, photo=True, video=True, + **kwargs, ): """Export image to path @@ -1053,6 +1042,7 @@ class LivePhotoAsset(PhotoAsset): overwrite: bool, if True, overwrites destination file if it already exists; default is False photo: bool, if True, export photo component of live photo video: bool, if True, export live video component of live photo + **kwargs: used only to avoid issues with each asset type having slightly different export arguments Returns: list of [path to exported image and/or video] @@ -1104,39 +1094,6 @@ class LivePhotoAsset(PhotoAsset): photo_output_file = pathlib.Path(increment_filename(photo_output_file)) video_output_file = pathlib.Path(increment_filename(video_output_file)) - # def handler(error): - # if error: - # raise PhotoKitExportError(f"writeDataForAssetResource error: {error}") - - # resource_manager = Photos.PHAssetResourceManager.defaultManager() - # options = Photos.PHAssetResourceRequestOptions.alloc().init() - # options.setNetworkAccessAllowed_(True) - # exported = [] - # Note: Tried writeDataForAssetResource_toFile_options_completionHandler_ which works - # but sets quarantine flag and for reasons I can't determine (maybe quarantine flag) - # causes pathlib.Path().is_file() to fail in tests - - # if photo: - # photo_output_url = path_to_NSURL(photo_output_file) - # resource_manager.writeDataForAssetResource_toFile_options_completionHandler_( - # photo_resource, photo_output_url, options, handler - # ) - # exported.append(str(photo_output_file)) - - # if video: - # video_output_url = path_to_NSURL(video_output_file) - # resource_manager.writeDataForAssetResource_toFile_options_completionHandler_( - # video_resource, video_output_url, options, handler - # ) - # exported.append(str(video_output_file)) - - # def completion_handler(error): - # if error: - # raise PhotoKitExportError(f"writeDataForAssetResource error: {error}") - - # would be nice to be able to usewriteDataForAssetResource_toFile_options_completionHandler_ - # but it sets quarantine flags that cause issues so instead, request the data and write the files directly - exported = [] if photo: data = self._request_resource_data(photo_resource) @@ -1155,41 +1112,6 @@ class LivePhotoAsset(PhotoAsset): request.dealloc() return exported - # def request_image_data(self, version=PHOTOS_VERSION_CURRENT): - # # Returns an NSImage which isn't overly useful - # # https://developer.apple.com/documentation/photokit/phimagemanager/1616964-requestimageforasset?language=objc - - # # requestImageForAsset:targetSize:contentMode:options:resultHandler: - - # options = Photos.PHImageRequestOptions.alloc().init() - # options.setVersion_(version) - # options.setNetworkAccessAllowed_(True) - # options.setSynchronous_(True) - # options.setDeliveryMode_( - # Photos.PHImageRequestOptionsDeliveryModeHighQualityFormat - # ) - - # event = threading.Event() - # image_data = ImageData() - - # def handler(result, info): - # nonlocal image_data - # if not info["PHImageResultIsDegradedKey"]: - # image_data.image_data = result - # image_data.info = info - # event.set() - - # self._manager.requestImageForAsset_targetSize_contentMode_options_resultHandler_( - # self._phasset, - # Photos.PHImageManagerMaximumSize, - # Photos.PHImageContentModeDefault, - # options, - # handler, - # ) - # event.wait() - # options.dealloc() - # return image_data - class PhotoLibrary: """Interface to PhotoKit PHImageManager and PHPhotoLibrary""" diff --git a/tests/test_cli.py b/tests/test_cli.py index 1c651eb5..45e57ffb 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -40,6 +40,11 @@ UUID_BURST_ALBUM = { ], } +UUID_SKIP_LIVE_PHOTOKIT = { + "54A01B04-16D7-4FDE-8860-19F2A641E433": ["IMG_3203_edited.jpeg"], + "1F3DF341-B822-4531-999E-724D642FD8E7": ["IMG_4179.jpeg"], +} + UUID_DOWNLOAD_MISSING = "C6C712C5-9316-408D-A3C3-125661422DA9" # IMG_8844.JPG UUID_FILE = "tests/uuid_from_file.txt" @@ -6645,6 +6650,43 @@ def test_export_download_missing_file_exists(): assert "exported: 1" in result.output +@pytest.mark.skipif( + "OSXPHOTOS_TEST_EXPORT" not in os.environ, + reason="Skip if not running on author's personal library.", +) +def test_export_skip_live_photokit(): + """test that --skip-live works with --use-photokit (issue #537)""" + import os + import os.path + import pathlib + + from osxphotos.cli import export + + runner = CliRunner() + cwd = os.getcwd() + # pylint: disable=not-context-manager + for uuid in UUID_SKIP_LIVE_PHOTOKIT: + with runner.isolated_filesystem(): + result = runner.invoke( + export, + [ + os.path.join(cwd, PHOTOS_DB_RHET), + ".", + "-V", + "--uuid", + uuid, + "--use-photos-export", + "--use-photokit", + "--skip-live", + "--skip-original-if-edited", + "--convert-to-jpeg", + ], + ) + assert result.exit_code == 0 + files = [str(p) for p in pathlib.Path(".").glob("IMG*")] + assert sorted(files) == sorted(UUID_SKIP_LIVE_PHOTOKIT[uuid]) + + def test_query_name(): """test query --name""" import json