Updated photokit code to work with raw+jpeg
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
""" version info """
|
""" version info """
|
||||||
|
|
||||||
__version__ = "0.42.48"
|
__version__ = "0.42.49"
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ MIN_SLEEP = 0.015
|
|||||||
|
|
||||||
### utility functions
|
### utility functions
|
||||||
def NSURL_to_path(url):
|
def NSURL_to_path(url):
|
||||||
""" Convert URL string as represented by NSURL to a path string """
|
"""Convert URL string as represented by NSURL to a path string"""
|
||||||
nsurl = Foundation.NSURL.alloc().initWithString_(
|
nsurl = Foundation.NSURL.alloc().initWithString_(
|
||||||
Foundation.NSString.alloc().initWithString_(str(url))
|
Foundation.NSString.alloc().initWithString_(str(url))
|
||||||
)
|
)
|
||||||
@@ -75,7 +75,7 @@ def NSURL_to_path(url):
|
|||||||
|
|
||||||
|
|
||||||
def path_to_NSURL(path):
|
def path_to_NSURL(path):
|
||||||
""" Convert path string to NSURL """
|
"""Convert path string to NSURL"""
|
||||||
pathstr = Foundation.NSString.alloc().initWithString_(str(path))
|
pathstr = Foundation.NSString.alloc().initWithString_(str(path))
|
||||||
url = Foundation.NSURL.fileURLWithPath_(pathstr)
|
url = Foundation.NSURL.fileURLWithPath_(pathstr)
|
||||||
pathstr.dealloc()
|
pathstr.dealloc()
|
||||||
@@ -83,10 +83,10 @@ def path_to_NSURL(path):
|
|||||||
|
|
||||||
|
|
||||||
def check_photokit_authorization():
|
def check_photokit_authorization():
|
||||||
""" Check authorization to use user's Photos Library
|
"""Check authorization to use user's Photos Library
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if user has authorized access to the Photos library, otherwise False
|
True if user has authorized access to the Photos library, otherwise False
|
||||||
"""
|
"""
|
||||||
|
|
||||||
auth_status = Photos.PHPhotoLibrary.authorizationStatus()
|
auth_status = Photos.PHPhotoLibrary.authorizationStatus()
|
||||||
@@ -94,7 +94,7 @@ def check_photokit_authorization():
|
|||||||
|
|
||||||
|
|
||||||
def request_photokit_authorization():
|
def request_photokit_authorization():
|
||||||
""" Request authorization to user's Photos Library
|
"""Request authorization to user's Photos Library
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
authorization status
|
authorization status
|
||||||
@@ -136,39 +136,39 @@ def request_photokit_authorization():
|
|||||||
|
|
||||||
### exceptions
|
### exceptions
|
||||||
class PhotoKitError(Exception):
|
class PhotoKitError(Exception):
|
||||||
"""Base class for exceptions in this module. """
|
"""Base class for exceptions in this module."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PhotoKitFetchFailed(PhotoKitError):
|
class PhotoKitFetchFailed(PhotoKitError):
|
||||||
"""Exception raised for errors in the input. """
|
"""Exception raised for errors in the input."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PhotoKitAuthError(PhotoKitError):
|
class PhotoKitAuthError(PhotoKitError):
|
||||||
"""Exception raised if unable to authorize use of PhotoKit. """
|
"""Exception raised if unable to authorize use of PhotoKit."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PhotoKitExportError(PhotoKitError):
|
class PhotoKitExportError(PhotoKitError):
|
||||||
"""Exception raised if unable to export asset. """
|
"""Exception raised if unable to export asset."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PhotoKitMediaTypeError(PhotoKitError):
|
class PhotoKitMediaTypeError(PhotoKitError):
|
||||||
""" Exception raised if an unknown mediaType() is encountered """
|
"""Exception raised if an unknown mediaType() is encountered"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
### helper classes
|
### helper classes
|
||||||
class ImageData:
|
class ImageData:
|
||||||
""" Simple class to hold the data passed to the handler for
|
"""Simple class to hold the data passed to the handler for
|
||||||
requestImageDataAndOrientationForAsset_options_resultHandler_
|
requestImageDataAndOrientationForAsset_options_resultHandler_
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -182,8 +182,7 @@ class ImageData:
|
|||||||
|
|
||||||
|
|
||||||
class AVAssetData:
|
class AVAssetData:
|
||||||
""" Simple class to hold the data passed to the handler for
|
"""Simple class to hold the data passed to the handler for"""
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.asset = None
|
self.asset = None
|
||||||
@@ -193,7 +192,7 @@ class AVAssetData:
|
|||||||
|
|
||||||
|
|
||||||
class PHAssetResourceData:
|
class PHAssetResourceData:
|
||||||
""" Simple class to hold data from
|
"""Simple class to hold data from
|
||||||
requestDataForAssetResource:options:dataReceivedHandler:completionHandler:
|
requestDataForAssetResource:options:dataReceivedHandler:completionHandler:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -212,8 +211,8 @@ class PHAssetResourceData:
|
|||||||
|
|
||||||
|
|
||||||
class PhotoKitNotificationDelegate(NSObject):
|
class PhotoKitNotificationDelegate(NSObject):
|
||||||
""" Handles notifications from NotificationCenter;
|
"""Handles notifications from NotificationCenter;
|
||||||
used with asynchronous PhotoKit requests to stop event loop when complete
|
used with asynchronous PhotoKit requests to stop event loop when complete
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def liveNotification_(self, note):
|
def liveNotification_(self, note):
|
||||||
@@ -227,11 +226,11 @@ class PhotoKitNotificationDelegate(NSObject):
|
|||||||
|
|
||||||
### main class implementation
|
### main class implementation
|
||||||
class PhotoAsset:
|
class PhotoAsset:
|
||||||
""" PhotoKit PHAsset representation """
|
"""PhotoKit PHAsset representation"""
|
||||||
|
|
||||||
def __init__(self, manager, phasset):
|
def __init__(self, manager, phasset):
|
||||||
""" Return a PhotoAsset object
|
"""Return a PhotoAsset object
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
manager = ImageManager object
|
manager = ImageManager object
|
||||||
phasset: a PHAsset object
|
phasset: a PHAsset object
|
||||||
@@ -242,32 +241,32 @@ class PhotoAsset:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def phasset(self):
|
def phasset(self):
|
||||||
""" Return PHAsset instance """
|
"""Return PHAsset instance"""
|
||||||
return self._phasset
|
return self._phasset
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def uuid(self):
|
def uuid(self):
|
||||||
""" Return local identifier (UUID) of PHAsset """
|
"""Return local identifier (UUID) of PHAsset"""
|
||||||
return self._phasset.localIdentifier()
|
return self._phasset.localIdentifier()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def isphoto(self):
|
def isphoto(self):
|
||||||
""" Return True if asset is photo (image), otherwise False """
|
"""Return True if asset is photo (image), otherwise False"""
|
||||||
return self.media_type == Photos.PHAssetMediaTypeImage
|
return self.media_type == Photos.PHAssetMediaTypeImage
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ismovie(self):
|
def ismovie(self):
|
||||||
""" Return True if asset is movie (video), otherwise False """
|
"""Return True if asset is movie (video), otherwise False"""
|
||||||
return self.media_type == Photos.PHAssetMediaTypeVideo
|
return self.media_type == Photos.PHAssetMediaTypeVideo
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def isaudio(self):
|
def isaudio(self):
|
||||||
""" Return True if asset is audio, otherwise False """
|
"""Return True if asset is audio, otherwise False"""
|
||||||
return self.media_type == Photos.PHAssetMediaTypeAudio
|
return self.media_type == Photos.PHAssetMediaTypeAudio
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def original_filename(self):
|
def original_filename(self):
|
||||||
""" Return original filename asset was imported with """
|
"""Return original filename asset was imported with"""
|
||||||
resources = self._resources()
|
resources = self._resources()
|
||||||
for resource in resources:
|
for resource in resources:
|
||||||
if (
|
if (
|
||||||
@@ -279,10 +278,22 @@ class PhotoAsset:
|
|||||||
return resource.originalFilename()
|
return resource.originalFilename()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def raw_filename(self):
|
||||||
|
"""Return RAW filename for RAW+JPEG photos or None if no RAW asset"""
|
||||||
|
resources = self._resources()
|
||||||
|
for resource in resources:
|
||||||
|
if (
|
||||||
|
self.isphoto
|
||||||
|
and resource.type() == Photos.PHAssetResourceTypeAlternatePhoto
|
||||||
|
):
|
||||||
|
return resource.originalFilename()
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hasadjustments(self):
|
def hasadjustments(self):
|
||||||
""" Check to see if a PHAsset has adjustment data associated with it
|
"""Check to see if a PHAsset has adjustment data associated with it
|
||||||
Returns False if no adjustments, True if any adjustments """
|
Returns False if no adjustments, True if any adjustments"""
|
||||||
|
|
||||||
# reference: https://developer.apple.com/documentation/photokit/phassetresource/1623988-assetresourcesforasset?language=objc
|
# reference: https://developer.apple.com/documentation/photokit/phassetresource/1623988-assetresourcesforasset?language=objc
|
||||||
|
|
||||||
@@ -299,112 +310,112 @@ class PhotoAsset:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def media_type(self):
|
def media_type(self):
|
||||||
""" media type such as image or video """
|
"""media type such as image or video"""
|
||||||
return self.phasset.mediaType()
|
return self.phasset.mediaType()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def media_subtypes(self):
|
def media_subtypes(self):
|
||||||
""" media subtype """
|
"""media subtype"""
|
||||||
return self.phasset.mediaSubtypes()
|
return self.phasset.mediaSubtypes()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def panorama(self):
|
def panorama(self):
|
||||||
""" return True if asset is panorama, otherwise False """
|
"""return True if asset is panorama, otherwise False"""
|
||||||
return bool(self.media_subtypes & Photos.PHAssetMediaSubtypePhotoPanorama)
|
return bool(self.media_subtypes & Photos.PHAssetMediaSubtypePhotoPanorama)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hdr(self):
|
def hdr(self):
|
||||||
""" return True if asset is HDR, otherwise False """
|
"""return True if asset is HDR, otherwise False"""
|
||||||
return bool(self.media_subtypes & Photos.PHAssetMediaSubtypePhotoHDR)
|
return bool(self.media_subtypes & Photos.PHAssetMediaSubtypePhotoHDR)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def screenshot(self):
|
def screenshot(self):
|
||||||
""" return True if asset is screenshot, otherwise False """
|
"""return True if asset is screenshot, otherwise False"""
|
||||||
return bool(self.media_subtypes & Photos.PHAssetMediaSubtypePhotoScreenshot)
|
return bool(self.media_subtypes & Photos.PHAssetMediaSubtypePhotoScreenshot)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def live(self):
|
def live(self):
|
||||||
""" return True if asset is live, otherwise False """
|
"""return True if asset is live, otherwise False"""
|
||||||
return bool(self.media_subtypes & Photos.PHAssetMediaSubtypePhotoLive)
|
return bool(self.media_subtypes & Photos.PHAssetMediaSubtypePhotoLive)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def streamed(self):
|
def streamed(self):
|
||||||
""" return True if asset is streamed video, otherwise False """
|
"""return True if asset is streamed video, otherwise False"""
|
||||||
return bool(self.media_subtypes & Photos.PHAssetMediaSubtypeVideoStreamed)
|
return bool(self.media_subtypes & Photos.PHAssetMediaSubtypeVideoStreamed)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def slow_mo(self):
|
def slow_mo(self):
|
||||||
""" return True if asset is slow motion (high frame rate) video, otherwise False """
|
"""return True if asset is slow motion (high frame rate) video, otherwise False"""
|
||||||
return bool(self.media_subtypes & Photos.PHAssetMediaSubtypeVideoHighFrameRate)
|
return bool(self.media_subtypes & Photos.PHAssetMediaSubtypeVideoHighFrameRate)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def time_lapse(self):
|
def time_lapse(self):
|
||||||
""" return True if asset is time lapse video, otherwise False """
|
"""return True if asset is time lapse video, otherwise False"""
|
||||||
return bool(self.media_subtypes & Photos.PHAssetMediaSubtypeVideoTimelapse)
|
return bool(self.media_subtypes & Photos.PHAssetMediaSubtypeVideoTimelapse)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def portrait(self):
|
def portrait(self):
|
||||||
""" return True if asset is portrait (depth effect), otherwise False """
|
"""return True if asset is portrait (depth effect), otherwise False"""
|
||||||
return bool(self.media_subtypes & Photos.PHAssetMediaSubtypePhotoDepthEffect)
|
return bool(self.media_subtypes & Photos.PHAssetMediaSubtypePhotoDepthEffect)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def burstid(self):
|
def burstid(self):
|
||||||
""" return burstIdentifier of image if image is burst photo otherwise None """
|
"""return burstIdentifier of image if image is burst photo otherwise None"""
|
||||||
return self.phasset.burstIdentifier()
|
return self.phasset.burstIdentifier()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def burst(self):
|
def burst(self):
|
||||||
""" return True if image is burst otherwise False """
|
"""return True if image is burst otherwise False"""
|
||||||
return bool(self.burstid)
|
return bool(self.burstid)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def source_type(self):
|
def source_type(self):
|
||||||
""" the means by which the asset entered the user's library """
|
"""the means by which the asset entered the user's library"""
|
||||||
return self.phasset.sourceType()
|
return self.phasset.sourceType()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pixel_width(self):
|
def pixel_width(self):
|
||||||
""" width in pixels """
|
"""width in pixels"""
|
||||||
return self.phasset.pixelWidth()
|
return self.phasset.pixelWidth()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pixel_height(self):
|
def pixel_height(self):
|
||||||
""" height in pixels """
|
"""height in pixels"""
|
||||||
return self.phasset.pixelHeight()
|
return self.phasset.pixelHeight()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def date(self):
|
def date(self):
|
||||||
""" date asset was created """
|
"""date asset was created"""
|
||||||
return self.phasset.creationDate()
|
return self.phasset.creationDate()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def date_modified(self):
|
def date_modified(self):
|
||||||
""" date asset was modified """
|
"""date asset was modified"""
|
||||||
return self.phasset.modificationDate()
|
return self.phasset.modificationDate()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def location(self):
|
def location(self):
|
||||||
""" location of the asset """
|
"""location of the asset"""
|
||||||
return self.phasset.location()
|
return self.phasset.location()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def duration(self):
|
def duration(self):
|
||||||
""" duration of the asset """
|
"""duration of the asset"""
|
||||||
return self.phasset.duration()
|
return self.phasset.duration()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def favorite(self):
|
def favorite(self):
|
||||||
""" True if asset is favorite, otherwise False """
|
"""True if asset is favorite, otherwise False"""
|
||||||
return self.phasset.isFavorite()
|
return self.phasset.isFavorite()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hidden(self):
|
def hidden(self):
|
||||||
""" True if asset is hidden, otherwise False """
|
"""True if asset is hidden, otherwise False"""
|
||||||
return self.phasset.isHidden()
|
return self.phasset.isHidden()
|
||||||
|
|
||||||
def metadata(self, version=PHOTOS_VERSION_CURRENT):
|
def metadata(self, version=PHOTOS_VERSION_CURRENT):
|
||||||
""" Return dict of asset metadata
|
"""Return dict of asset metadata
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
|
version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
|
||||||
"""
|
"""
|
||||||
@@ -412,17 +423,28 @@ class PhotoAsset:
|
|||||||
return imagedata.metadata
|
return imagedata.metadata
|
||||||
|
|
||||||
def uti(self, version=PHOTOS_VERSION_CURRENT):
|
def uti(self, version=PHOTOS_VERSION_CURRENT):
|
||||||
""" Return UTI of asset
|
"""Return UTI of asset
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
|
version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
|
||||||
"""
|
"""
|
||||||
imagedata = self._request_image_data(version=version)
|
imagedata = self._request_image_data(version=version)
|
||||||
return imagedata.uti
|
return imagedata.uti
|
||||||
|
|
||||||
|
def uti_raw(self):
|
||||||
|
"""Return UTI of RAW component of RAW+JPEG pair"""
|
||||||
|
resources = self._resources()
|
||||||
|
for resource in resources:
|
||||||
|
if (
|
||||||
|
self.isphoto
|
||||||
|
and resource.type() == Photos.PHAssetResourceTypeAlternatePhoto
|
||||||
|
):
|
||||||
|
return resource.uniformTypeIdentifier()
|
||||||
|
return None
|
||||||
|
|
||||||
def url(self, version=PHOTOS_VERSION_CURRENT):
|
def url(self, version=PHOTOS_VERSION_CURRENT):
|
||||||
""" Return URL of asset
|
"""Return URL of asset
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
|
version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
|
||||||
"""
|
"""
|
||||||
@@ -430,8 +452,8 @@ class PhotoAsset:
|
|||||||
return str(imagedata.info["PHImageFileURLKey"])
|
return str(imagedata.info["PHImageFileURLKey"])
|
||||||
|
|
||||||
def path(self, version=PHOTOS_VERSION_CURRENT):
|
def path(self, version=PHOTOS_VERSION_CURRENT):
|
||||||
""" Return path of asset
|
"""Return path of asset
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
|
version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
|
||||||
"""
|
"""
|
||||||
@@ -440,8 +462,8 @@ class PhotoAsset:
|
|||||||
return url.fileSystemRepresentation().decode("utf-8")
|
return url.fileSystemRepresentation().decode("utf-8")
|
||||||
|
|
||||||
def orientation(self, version=PHOTOS_VERSION_CURRENT):
|
def orientation(self, version=PHOTOS_VERSION_CURRENT):
|
||||||
""" Return orientation of asset
|
"""Return orientation of asset
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
|
version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
|
||||||
"""
|
"""
|
||||||
@@ -450,8 +472,8 @@ class PhotoAsset:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def degraded(self, version=PHOTOS_VERSION_CURRENT):
|
def degraded(self, version=PHOTOS_VERSION_CURRENT):
|
||||||
""" Return True if asset is degraded version
|
"""Return True if asset is degraded version
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
|
version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
|
||||||
"""
|
"""
|
||||||
@@ -459,15 +481,21 @@ class PhotoAsset:
|
|||||||
return imagedata.info["PHImageResultIsDegradedKey"]
|
return imagedata.info["PHImageResultIsDegradedKey"]
|
||||||
|
|
||||||
def export(
|
def export(
|
||||||
self, dest, filename=None, version=PHOTOS_VERSION_CURRENT, overwrite=False
|
self,
|
||||||
|
dest,
|
||||||
|
filename=None,
|
||||||
|
version=PHOTOS_VERSION_CURRENT,
|
||||||
|
overwrite=False,
|
||||||
|
raw=False,
|
||||||
):
|
):
|
||||||
""" Export image to path
|
"""Export image to path
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
dest: str, path to destination directory
|
dest: str, path to destination directory
|
||||||
filename: str, optional name of exported file; if not provided, defaults to asset's original filename
|
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)
|
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
|
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
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of path to exported image(s)
|
List of path to exported image(s)
|
||||||
@@ -492,11 +520,28 @@ class PhotoAsset:
|
|||||||
|
|
||||||
output_file = None
|
output_file = None
|
||||||
if self.isphoto:
|
if self.isphoto:
|
||||||
imagedata = self._request_image_data(version=version)
|
# will hold exported image data and needs to be cleaned up at end
|
||||||
if not imagedata.image_data:
|
imagedata = None
|
||||||
raise PhotoKitExportError("Could not get image data")
|
if raw:
|
||||||
|
# export the raw component
|
||||||
ext = get_preferred_uti_extension(imagedata.uti)
|
resources = self._resources()
|
||||||
|
for resource in resources:
|
||||||
|
if resource.type() == Photos.PHAssetResourceTypeAlternatePhoto:
|
||||||
|
data = self._request_resource_data(resource)
|
||||||
|
ext = pathlib.Path(self.raw_filename).suffix[1:]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise PhotoKitExportError(
|
||||||
|
"Could not get image data for RAW photo"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# TODO: if user has selected use RAW as original, this returns the RAW
|
||||||
|
# can get the jpeg with resource.type() == Photos.PHAssetResourceTypePhoto
|
||||||
|
imagedata = self._request_image_data(version=version)
|
||||||
|
if not imagedata.image_data:
|
||||||
|
raise PhotoKitExportError("Could not get image data")
|
||||||
|
ext = get_preferred_uti_extension(imagedata.uti)
|
||||||
|
data = imagedata.image_data
|
||||||
|
|
||||||
output_file = dest / f"{filename.stem}.{ext}"
|
output_file = dest / f"{filename.stem}.{ext}"
|
||||||
|
|
||||||
@@ -504,7 +549,9 @@ class PhotoAsset:
|
|||||||
output_file = pathlib.Path(increment_filename(output_file))
|
output_file = pathlib.Path(increment_filename(output_file))
|
||||||
|
|
||||||
with open(output_file, "wb") as fd:
|
with open(output_file, "wb") as fd:
|
||||||
fd.write(imagedata.image_data)
|
fd.write(data)
|
||||||
|
|
||||||
|
if imagedata:
|
||||||
del imagedata
|
del imagedata
|
||||||
elif self.ismovie:
|
elif self.ismovie:
|
||||||
videodata = self._request_video_data(version=version)
|
videodata = self._request_video_data(version=version)
|
||||||
@@ -526,14 +573,14 @@ class PhotoAsset:
|
|||||||
return [str(output_file)]
|
return [str(output_file)]
|
||||||
|
|
||||||
def _request_image_data(self, version=PHOTOS_VERSION_ORIGINAL):
|
def _request_image_data(self, version=PHOTOS_VERSION_ORIGINAL):
|
||||||
""" Request image data and metadata for self._phasset
|
"""Request image data and metadata for self._phasset
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
version: which version to request
|
version: which version to request
|
||||||
PHOTOS_VERSION_ORIGINAL (default), request original highest fidelity version
|
PHOTOS_VERSION_ORIGINAL (default), request original highest fidelity version
|
||||||
PHOTOS_VERSION_CURRENT, request current version with all edits
|
PHOTOS_VERSION_CURRENT, request current version with all edits
|
||||||
PHOTOS_VERSION_UNADJUSTED, request highest quality unadjusted version
|
PHOTOS_VERSION_UNADJUSTED, request highest quality unadjusted version
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
ImageData instance
|
ImageData instance
|
||||||
|
|
||||||
@@ -563,8 +610,8 @@ class PhotoAsset:
|
|||||||
event = threading.Event()
|
event = threading.Event()
|
||||||
|
|
||||||
def handler(imageData, dataUTI, orientation, info):
|
def handler(imageData, dataUTI, orientation, info):
|
||||||
""" result handler for requestImageDataAndOrientationForAsset_options_resultHandler_
|
"""result handler for requestImageDataAndOrientationForAsset_options_resultHandler_
|
||||||
all returned by the request is set as properties of nonlocal data (Fetchdata object) """
|
all returned by the request is set as properties of nonlocal data (Fetchdata object)"""
|
||||||
|
|
||||||
nonlocal requestdata
|
nonlocal requestdata
|
||||||
|
|
||||||
@@ -594,19 +641,63 @@ class PhotoAsset:
|
|||||||
del requestdata
|
del requestdata
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def _request_resource_data(self, resource):
|
||||||
|
"""Request asset resource data (either photo or video component)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
resource: PHAssetResource to request
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
"""
|
||||||
|
|
||||||
|
with objc.autorelease_pool():
|
||||||
|
resource_manager = Photos.PHAssetResourceManager.defaultManager()
|
||||||
|
options = Photos.PHAssetResourceRequestOptions.alloc().init()
|
||||||
|
options.setNetworkAccessAllowed_(True)
|
||||||
|
|
||||||
|
requestdata = PHAssetResourceData()
|
||||||
|
event = threading.Event()
|
||||||
|
|
||||||
|
def handler(data):
|
||||||
|
"""result handler for requestImageDataAndOrientationForAsset_options_resultHandler_
|
||||||
|
all returned by the request is set as properties of nonlocal data (Fetchdata object)"""
|
||||||
|
|
||||||
|
nonlocal requestdata
|
||||||
|
|
||||||
|
requestdata.data += data
|
||||||
|
|
||||||
|
def completion_handler(error):
|
||||||
|
if error:
|
||||||
|
raise PhotoKitExportError(
|
||||||
|
"Error requesting data for asset resource"
|
||||||
|
)
|
||||||
|
event.set()
|
||||||
|
|
||||||
|
resource_manager.requestDataForAssetResource_options_dataReceivedHandler_completionHandler_(
|
||||||
|
resource, options, handler, completion_handler
|
||||||
|
)
|
||||||
|
|
||||||
|
event.wait()
|
||||||
|
|
||||||
|
# not sure why this is needed -- some weird ref count thing maybe
|
||||||
|
# if I don't do this, memory leaks
|
||||||
|
data = copy.copy(requestdata.data)
|
||||||
|
del requestdata
|
||||||
|
return data
|
||||||
|
|
||||||
def _make_result_handle_(self, data):
|
def _make_result_handle_(self, data):
|
||||||
""" Make handler function and threading event to use with
|
"""Make handler function and threading event to use with
|
||||||
requestImageDataAndOrientationForAsset_options_resultHandler_
|
requestImageDataAndOrientationForAsset_options_resultHandler_
|
||||||
data: Fetchdata class to hold resulting metadata
|
data: Fetchdata class to hold resulting metadata
|
||||||
returns: handler function, threading.Event() instance
|
returns: handler function, threading.Event() instance
|
||||||
Following call to requestImageDataAndOrientationForAsset_options_resultHandler_,
|
Following call to requestImageDataAndOrientationForAsset_options_resultHandler_,
|
||||||
data will hold data from the fetch """
|
data will hold data from the fetch"""
|
||||||
|
|
||||||
event = threading.Event()
|
event = threading.Event()
|
||||||
|
|
||||||
def handler(imageData, dataUTI, orientation, info):
|
def handler(imageData, dataUTI, orientation, info):
|
||||||
""" result handler for requestImageDataAndOrientationForAsset_options_resultHandler_
|
"""result handler for requestImageDataAndOrientationForAsset_options_resultHandler_
|
||||||
all returned by the request is set as properties of nonlocal data (Fetchdata object) """
|
all returned by the request is set as properties of nonlocal data (Fetchdata object)"""
|
||||||
|
|
||||||
nonlocal data
|
nonlocal data
|
||||||
|
|
||||||
@@ -627,14 +718,14 @@ class PhotoAsset:
|
|||||||
return handler, event
|
return handler, event
|
||||||
|
|
||||||
def _resources(self):
|
def _resources(self):
|
||||||
""" Return list of PHAssetResource for object """
|
"""Return list of PHAssetResource for object"""
|
||||||
resources = Photos.PHAssetResource.assetResourcesForAsset_(self.phasset)
|
resources = Photos.PHAssetResource.assetResourcesForAsset_(self.phasset)
|
||||||
return [resources.objectAtIndex_(idx) for idx in range(resources.count())]
|
return [resources.objectAtIndex_(idx) for idx in range(resources.count())]
|
||||||
|
|
||||||
|
|
||||||
class SlowMoVideoExporter(NSObject):
|
class SlowMoVideoExporter(NSObject):
|
||||||
def initWithAVAsset_path_(self, avasset, path):
|
def initWithAVAsset_path_(self, avasset, path):
|
||||||
""" init helper class for exporting slow-mo video
|
"""init helper class for exporting slow-mo video
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
avasset: AVAsset
|
avasset: AVAsset
|
||||||
@@ -649,15 +740,17 @@ class SlowMoVideoExporter(NSObject):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def exportSlowMoVideo(self):
|
def exportSlowMoVideo(self):
|
||||||
""" export slow-mo video with AVAssetExportSession
|
"""export slow-mo video with AVAssetExportSession
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
path to exported file
|
path to exported file
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with objc.autorelease_pool():
|
with objc.autorelease_pool():
|
||||||
exporter = AVFoundation.AVAssetExportSession.alloc().initWithAsset_presetName_(
|
exporter = (
|
||||||
self.avasset, AVFoundation.AVAssetExportPresetHighestQuality
|
AVFoundation.AVAssetExportSession.alloc().initWithAsset_presetName_(
|
||||||
|
self.avasset, AVFoundation.AVAssetExportPresetHighestQuality
|
||||||
|
)
|
||||||
)
|
)
|
||||||
exporter.setOutputURL_(self.url)
|
exporter.setOutputURL_(self.url)
|
||||||
exporter.setOutputFileType_(AVFoundation.AVFileTypeQuickTimeMovie)
|
exporter.setOutputFileType_(AVFoundation.AVFileTypeQuickTimeMovie)
|
||||||
@@ -666,7 +759,7 @@ class SlowMoVideoExporter(NSObject):
|
|||||||
self.done = False
|
self.done = False
|
||||||
|
|
||||||
def handler():
|
def handler():
|
||||||
""" result handler for exportAsynchronouslyWithCompletionHandler """
|
"""result handler for exportAsynchronouslyWithCompletionHandler"""
|
||||||
self.done = True
|
self.done = True
|
||||||
|
|
||||||
exporter.exportAsynchronouslyWithCompletionHandler_(handler)
|
exporter.exportAsynchronouslyWithCompletionHandler_(handler)
|
||||||
@@ -700,7 +793,7 @@ class SlowMoVideoExporter(NSObject):
|
|||||||
|
|
||||||
|
|
||||||
class VideoAsset(PhotoAsset):
|
class VideoAsset(PhotoAsset):
|
||||||
""" PhotoKit PHAsset representation of video asset """
|
"""PhotoKit PHAsset representation of video asset"""
|
||||||
|
|
||||||
# TODO: doesn't work for slow-mo videos
|
# TODO: doesn't work for slow-mo videos
|
||||||
# see https://stackoverflow.com/questions/26152396/how-to-access-nsdata-nsurl-of-slow-motion-videos-using-photokit
|
# see https://stackoverflow.com/questions/26152396/how-to-access-nsdata-nsurl-of-slow-motion-videos-using-photokit
|
||||||
@@ -710,7 +803,7 @@ class VideoAsset(PhotoAsset):
|
|||||||
def export(
|
def export(
|
||||||
self, dest, filename=None, version=PHOTOS_VERSION_CURRENT, overwrite=False
|
self, dest, filename=None, version=PHOTOS_VERSION_CURRENT, overwrite=False
|
||||||
):
|
):
|
||||||
""" Export video to path
|
"""Export video to path
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
dest: str, path to destination directory
|
dest: str, path to destination directory
|
||||||
@@ -766,7 +859,7 @@ class VideoAsset(PhotoAsset):
|
|||||||
def _export_slow_mo(
|
def _export_slow_mo(
|
||||||
self, dest, filename=None, version=PHOTOS_VERSION_CURRENT, overwrite=False
|
self, dest, filename=None, version=PHOTOS_VERSION_CURRENT, overwrite=False
|
||||||
):
|
):
|
||||||
""" Export slow-motion video to path
|
"""Export slow-motion video to path
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
dest: str, path to destination directory
|
dest: str, path to destination directory
|
||||||
@@ -815,14 +908,14 @@ class VideoAsset(PhotoAsset):
|
|||||||
|
|
||||||
# todo: rewrite this with NotificationCenter and App event loop?
|
# todo: rewrite this with NotificationCenter and App event loop?
|
||||||
def _request_video_data(self, version=PHOTOS_VERSION_ORIGINAL):
|
def _request_video_data(self, version=PHOTOS_VERSION_ORIGINAL):
|
||||||
""" Request video data for self._phasset
|
"""Request video data for self._phasset
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
version: which version to request
|
version: which version to request
|
||||||
PHOTOS_VERSION_ORIGINAL (default), request original highest fidelity version
|
PHOTOS_VERSION_ORIGINAL (default), request original highest fidelity version
|
||||||
PHOTOS_VERSION_CURRENT, request current version with all edits
|
PHOTOS_VERSION_CURRENT, request current version with all edits
|
||||||
PHOTOS_VERSION_UNADJUSTED, request highest quality unadjusted version
|
PHOTOS_VERSION_UNADJUSTED, request highest quality unadjusted version
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError if passed invalid value for version
|
ValueError if passed invalid value for version
|
||||||
"""
|
"""
|
||||||
@@ -844,7 +937,7 @@ class VideoAsset(PhotoAsset):
|
|||||||
event = threading.Event()
|
event = threading.Event()
|
||||||
|
|
||||||
def handler(asset, audiomix, info):
|
def handler(asset, audiomix, info):
|
||||||
""" result handler for requestAVAssetForVideo:asset options:options resultHandler """
|
"""result handler for requestAVAssetForVideo:asset options:options resultHandler"""
|
||||||
nonlocal requestdata
|
nonlocal requestdata
|
||||||
|
|
||||||
requestdata.asset = asset
|
requestdata.asset = asset
|
||||||
@@ -866,8 +959,8 @@ class VideoAsset(PhotoAsset):
|
|||||||
|
|
||||||
|
|
||||||
class LivePhotoRequest(NSObject):
|
class LivePhotoRequest(NSObject):
|
||||||
""" Manage requests for live photo assets
|
"""Manage requests for live photo assets
|
||||||
See: https://developer.apple.com/documentation/photokit/phimagemanager/1616984-requestlivephotoforasset?language=objc
|
See: https://developer.apple.com/documentation/photokit/phimagemanager/1616984-requestlivephotoforasset?language=objc
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def initWithManager_Asset_(self, manager, asset):
|
def initWithManager_Asset_(self, manager, asset):
|
||||||
@@ -880,7 +973,7 @@ class LivePhotoRequest(NSObject):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def requestLivePhotoResources(self, version=PHOTOS_VERSION_CURRENT):
|
def requestLivePhotoResources(self, version=PHOTOS_VERSION_CURRENT):
|
||||||
""" return the photos and video components of a live video as [PHAssetResource] """
|
"""return the photos and video components of a live video as [PHAssetResource]"""
|
||||||
|
|
||||||
with objc.autorelease_pool():
|
with objc.autorelease_pool():
|
||||||
options = Photos.PHLivePhotoRequestOptions.alloc().init()
|
options = Photos.PHLivePhotoRequestOptions.alloc().init()
|
||||||
@@ -898,7 +991,7 @@ class LivePhotoRequest(NSObject):
|
|||||||
self.live_photo = None
|
self.live_photo = None
|
||||||
|
|
||||||
def handler(result, info):
|
def handler(result, info):
|
||||||
""" result handler for requestLivePhotoForAsset:targetSize:contentMode:options:resultHandler: """
|
"""result handler for requestLivePhotoForAsset:targetSize:contentMode:options:resultHandler:"""
|
||||||
if not info["PHImageResultIsDegradedKey"]:
|
if not info["PHImageResultIsDegradedKey"]:
|
||||||
self.live_photo = result
|
self.live_photo = result
|
||||||
self.info = info
|
self.info = info
|
||||||
@@ -940,7 +1033,7 @@ class LivePhotoRequest(NSObject):
|
|||||||
|
|
||||||
|
|
||||||
class LivePhotoAsset(PhotoAsset):
|
class LivePhotoAsset(PhotoAsset):
|
||||||
""" Represents a live photo """
|
"""Represents a live photo"""
|
||||||
|
|
||||||
def export(
|
def export(
|
||||||
self,
|
self,
|
||||||
@@ -951,7 +1044,7 @@ class LivePhotoAsset(PhotoAsset):
|
|||||||
photo=True,
|
photo=True,
|
||||||
video=True,
|
video=True,
|
||||||
):
|
):
|
||||||
""" Export image to path
|
"""Export image to path
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
dest: str, path to destination directory
|
dest: str, path to destination directory
|
||||||
@@ -1062,50 +1155,6 @@ class LivePhotoAsset(PhotoAsset):
|
|||||||
request.dealloc()
|
request.dealloc()
|
||||||
return exported
|
return exported
|
||||||
|
|
||||||
def _request_resource_data(self, resource):
|
|
||||||
""" Request asset resource data (either photo or video component)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
resource: PHAssetResource to request
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
"""
|
|
||||||
|
|
||||||
with objc.autorelease_pool():
|
|
||||||
resource_manager = Photos.PHAssetResourceManager.defaultManager()
|
|
||||||
options = Photos.PHAssetResourceRequestOptions.alloc().init()
|
|
||||||
options.setNetworkAccessAllowed_(True)
|
|
||||||
|
|
||||||
requestdata = PHAssetResourceData()
|
|
||||||
event = threading.Event()
|
|
||||||
|
|
||||||
def handler(data):
|
|
||||||
""" result handler for requestImageDataAndOrientationForAsset_options_resultHandler_
|
|
||||||
all returned by the request is set as properties of nonlocal data (Fetchdata object) """
|
|
||||||
|
|
||||||
nonlocal requestdata
|
|
||||||
|
|
||||||
requestdata.data += data
|
|
||||||
|
|
||||||
def completion_handler(error):
|
|
||||||
if error:
|
|
||||||
raise PhotoKitExportError(
|
|
||||||
"Error requesting data for asset resource"
|
|
||||||
)
|
|
||||||
event.set()
|
|
||||||
|
|
||||||
resource_manager.requestDataForAssetResource_options_dataReceivedHandler_completionHandler_(
|
|
||||||
resource, options, handler, completion_handler
|
|
||||||
)
|
|
||||||
|
|
||||||
event.wait()
|
|
||||||
|
|
||||||
# not sure why this is needed -- some weird ref count thing maybe
|
|
||||||
# if I don't do this, memory leaks
|
|
||||||
data = copy.copy(requestdata.data)
|
|
||||||
del requestdata
|
|
||||||
return data
|
|
||||||
|
|
||||||
# def request_image_data(self, version=PHOTOS_VERSION_CURRENT):
|
# def request_image_data(self, version=PHOTOS_VERSION_CURRENT):
|
||||||
# # Returns an NSImage which isn't overly useful
|
# # Returns an NSImage which isn't overly useful
|
||||||
# # https://developer.apple.com/documentation/photokit/phimagemanager/1616964-requestimageforasset?language=objc
|
# # https://developer.apple.com/documentation/photokit/phimagemanager/1616964-requestimageforasset?language=objc
|
||||||
@@ -1143,12 +1192,12 @@ class LivePhotoAsset(PhotoAsset):
|
|||||||
|
|
||||||
|
|
||||||
class PhotoLibrary:
|
class PhotoLibrary:
|
||||||
""" Interface to PhotoKit PHImageManager and PHPhotoLibrary """
|
"""Interface to PhotoKit PHImageManager and PHPhotoLibrary"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
""" Initialize ImageManager instance. Requests authorization to use the
|
"""Initialize ImageManager instance. Requests authorization to use the
|
||||||
Photos library if authorization has not already been granted.
|
Photos library if authorization has not already been granted.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
PhotoKitAuthError if unable to authorize access to PhotoKit
|
PhotoKitAuthError if unable to authorize access to PhotoKit
|
||||||
"""
|
"""
|
||||||
@@ -1167,7 +1216,7 @@ class PhotoLibrary:
|
|||||||
self._phimagemanager = Photos.PHCachingImageManager.defaultManager()
|
self._phimagemanager = Photos.PHCachingImageManager.defaultManager()
|
||||||
|
|
||||||
def request_authorization(self):
|
def request_authorization(self):
|
||||||
""" Request authorization to user's Photos Library
|
"""Request authorization to user's Photos Library
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
authorization status
|
authorization status
|
||||||
@@ -1177,7 +1226,7 @@ class PhotoLibrary:
|
|||||||
return self.auth_status
|
return self.auth_status
|
||||||
|
|
||||||
def fetch_uuid_list(self, uuid_list):
|
def fetch_uuid_list(self, uuid_list):
|
||||||
""" fetch PHAssets with uuids in uuid_list
|
"""fetch PHAssets with uuids in uuid_list
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
uuid_list: list of str (UUID of image assets to fetch)
|
uuid_list: list of str (UUID of image assets to fetch)
|
||||||
@@ -1206,7 +1255,7 @@ class PhotoLibrary:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def fetch_uuid(self, uuid):
|
def fetch_uuid(self, uuid):
|
||||||
""" fetch PHAsset with uuid = uuid
|
"""fetch PHAsset with uuid = uuid
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
uuid: str; UUID of image asset to fetch
|
uuid: str; UUID of image asset to fetch
|
||||||
@@ -1224,8 +1273,8 @@ class PhotoLibrary:
|
|||||||
raise PhotoKitFetchFailed(f"Fetch did not return result for uuid {uuid}")
|
raise PhotoKitFetchFailed(f"Fetch did not return result for uuid {uuid}")
|
||||||
|
|
||||||
def fetch_burst_uuid(self, burstid, all=False):
|
def fetch_burst_uuid(self, burstid, all=False):
|
||||||
""" fetch PhotoAssets with burst ID = burstid
|
"""fetch PhotoAssets with burst ID = burstid
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
burstid: str, burst UUID
|
burstid: str, burst UUID
|
||||||
all: return all burst assets; if False returns only those selected by the user (including the "key photo" even if user hasn't manually selected it)
|
all: return all burst assets; if False returns only those selected by the user (including the "key photo" even if user hasn't manually selected it)
|
||||||
@@ -1254,11 +1303,11 @@ class PhotoLibrary:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _asset_factory(self, phasset):
|
def _asset_factory(self, phasset):
|
||||||
""" creates a PhotoAsset, VideoAsset, or LivePhotoAsset
|
"""creates a PhotoAsset, VideoAsset, or LivePhotoAsset
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
phasset: PHAsset object
|
phasset: PHAsset object
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
PhotoAsset, VideoAsset, or LivePhotoAsset depending on type of PHAsset
|
PhotoAsset, VideoAsset, or LivePhotoAsset depending on type of PHAsset
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -57,6 +57,14 @@ UUID_DICT = {
|
|||||||
"burst_selected": 4,
|
"burst_selected": 4,
|
||||||
"burst_all": 5,
|
"burst_all": 5,
|
||||||
},
|
},
|
||||||
|
"raw+jpeg": {
|
||||||
|
"uuid": "E3DD04AF-CB65-4D9B-BB79-FF4C955533DB",
|
||||||
|
"filename": "IMG_1994.JPG",
|
||||||
|
"raw_filename": "IMG_1994.CR2",
|
||||||
|
"unadjusted_size": 16128420,
|
||||||
|
"uti_raw": "com.canon.cr2-raw-image",
|
||||||
|
"uti": "public.jpeg",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -78,10 +86,23 @@ def test_plain_photo():
|
|||||||
lib = PhotoLibrary()
|
lib = PhotoLibrary()
|
||||||
photo = lib.fetch_uuid(uuid)
|
photo = lib.fetch_uuid(uuid)
|
||||||
assert photo.original_filename == filename
|
assert photo.original_filename == filename
|
||||||
|
assert photo.raw_filename is None
|
||||||
assert photo.isphoto
|
assert photo.isphoto
|
||||||
assert not photo.ismovie
|
assert not photo.ismovie
|
||||||
|
|
||||||
|
|
||||||
|
def test_raw_plus_jpeg():
|
||||||
|
"""test RAW+JPEG"""
|
||||||
|
uuid = UUID_DICT["raw+jpeg"]["uuid"]
|
||||||
|
|
||||||
|
lib = PhotoLibrary()
|
||||||
|
photo = lib.fetch_uuid(uuid)
|
||||||
|
assert photo.original_filename == UUID_DICT["raw+jpeg"]["filename"]
|
||||||
|
assert photo.raw_filename == UUID_DICT["raw+jpeg"]["raw_filename"]
|
||||||
|
assert photo.uti_raw() == UUID_DICT["raw+jpeg"]["uti_raw"]
|
||||||
|
assert photo.uti() == UUID_DICT["raw+jpeg"]["uti"]
|
||||||
|
|
||||||
|
|
||||||
def test_hdr():
|
def test_hdr():
|
||||||
"""test hdr"""
|
"""test hdr"""
|
||||||
uuid = UUID_DICT["hdr"]["uuid"]
|
uuid = UUID_DICT["hdr"]["uuid"]
|
||||||
@@ -196,6 +217,22 @@ def test_export_photo_current():
|
|||||||
assert export_path.stat().st_size == test_dict["adjusted_size"]
|
assert export_path.stat().st_size == test_dict["adjusted_size"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_export_photo_raw():
|
||||||
|
"""test PhotoAsset.export for raw component"""
|
||||||
|
test_dict = UUID_DICT["raw+jpeg"]
|
||||||
|
uuid = test_dict["uuid"]
|
||||||
|
lib = PhotoLibrary()
|
||||||
|
photo = lib.fetch_uuid(uuid)
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory(prefix="photokit_test") as tempdir:
|
||||||
|
export_path = photo.export(tempdir, raw=True)
|
||||||
|
export_path = pathlib.Path(export_path[0])
|
||||||
|
assert export_path.is_file()
|
||||||
|
filename = test_dict["raw_filename"]
|
||||||
|
assert export_path.stem == pathlib.Path(filename).stem
|
||||||
|
assert export_path.stat().st_size == test_dict["unadjusted_size"]
|
||||||
|
|
||||||
|
|
||||||
### VideoAsset
|
### VideoAsset
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user