More refactoring of export code, #462
This commit is contained in:
@@ -4207,8 +4207,12 @@ def repl(ctx, cli_obj, db, emacs):
|
|||||||
|
|
||||||
from osxphotos import ExifTool, PhotoInfo, PhotosDB
|
from osxphotos import ExifTool, PhotoInfo, PhotosDB
|
||||||
from osxphotos.albuminfo import AlbumInfo
|
from osxphotos.albuminfo import AlbumInfo
|
||||||
|
from osxphotos.momentinfo import MomentInfo
|
||||||
|
from osxphotos.photoexporter import ExportResults, PhotoExporter
|
||||||
from osxphotos.placeinfo import PlaceInfo
|
from osxphotos.placeinfo import PlaceInfo
|
||||||
from osxphotos.queryoptions import QueryOptions
|
from osxphotos.queryoptions import QueryOptions
|
||||||
|
from osxphotos.scoreinfo import ScoreInfo
|
||||||
|
from osxphotos.searchinfo import SearchInfo
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
logger.disabled = True
|
logger.disabled = True
|
||||||
@@ -4241,7 +4245,9 @@ def repl(ctx, cli_obj, db, emacs):
|
|||||||
|
|
||||||
print(f"Found {len(photos)} photos in {tictoc:0.2f} seconds\n")
|
print(f"Found {len(photos)} photos in {tictoc:0.2f} seconds\n")
|
||||||
print("The following classes have been imported from osxphotos:")
|
print("The following classes have been imported from osxphotos:")
|
||||||
print("- AlbumInfo, ExifTool, PhotoInfo, PhotosDB, PlaceInfo, QueryOptions\n")
|
print(
|
||||||
|
"- AlbumInfo, ExifTool, PhotoInfo, PhotoExporter, ExportResults, PhotosDB, PlaceInfo, QueryOptions, MomentInfo, ScoreInfo, SearchInfo\n"
|
||||||
|
)
|
||||||
print("The following variables are defined:")
|
print("The following variables are defined:")
|
||||||
print(f"- photosdb: PhotosDB() instance for {photosdb.library_path}")
|
print(f"- photosdb: PhotosDB() instance for {photosdb.library_path}")
|
||||||
print(
|
print(
|
||||||
|
|||||||
@@ -369,7 +369,7 @@ class PhotoExporter:
|
|||||||
e.g. to get the extension of the edited photo,
|
e.g. to get the extension of the edited photo,
|
||||||
reference PhotoInfo.path_edited
|
reference PhotoInfo.path_edited
|
||||||
original: (boolean, default=True); if True, will export the original version of the photo
|
original: (boolean, default=True); if True, will export the original version of the photo
|
||||||
edited: (boolean, default=False); if True will export the edited version of the photo
|
edited: (boolean, default=False); if True will export the edited version of the photo (only one of original or edited can be used)
|
||||||
live_photo: (boolean, default=False); if True, will also export the associated .mov for live photos
|
live_photo: (boolean, default=False); if True, will also export the associated .mov for live photos
|
||||||
raw_photo: (boolean, default=False); if True, will also export the associated RAW photo
|
raw_photo: (boolean, default=False); if True, will also export the associated RAW photo
|
||||||
export_as_hardlink: (boolean, default=False); if True, will hardlink files instead of copying them
|
export_as_hardlink: (boolean, default=False); if True, will hardlink files instead of copying them
|
||||||
@@ -445,7 +445,6 @@ class PhotoExporter:
|
|||||||
|
|
||||||
# NOTE: This function is very complex and does a lot of things.
|
# NOTE: This function is very complex and does a lot of things.
|
||||||
# Don't modify this code if you don't fully understand everything it does.
|
# Don't modify this code if you don't fully understand everything it does.
|
||||||
# TODO: This is a good candidate for refactoring.
|
|
||||||
|
|
||||||
# when called from export(), won't get an export_db, so use no-op version
|
# when called from export(), won't get an export_db, so use no-op version
|
||||||
if export_db is None:
|
if export_db is None:
|
||||||
@@ -461,7 +460,9 @@ class PhotoExporter:
|
|||||||
|
|
||||||
export_original = original
|
export_original = original
|
||||||
export_edited = edited
|
export_edited = edited
|
||||||
if edited and not self.photo.hasadjustments:
|
if export_original and export_edited:
|
||||||
|
raise ValueError("Cannot export both original and edited photos")
|
||||||
|
if export_edited and not self.photo.hasadjustments:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Photo does not have adjustments, cannot export edited version"
|
"Photo does not have adjustments, cannot export edited version"
|
||||||
)
|
)
|
||||||
@@ -475,24 +476,12 @@ class PhotoExporter:
|
|||||||
original_filename = original_filename or self.photo.original_filename
|
original_filename = original_filename or self.photo.original_filename
|
||||||
dest_original = pathlib.Path(dest) / original_filename
|
dest_original = pathlib.Path(dest) / original_filename
|
||||||
|
|
||||||
if not edited_filename:
|
edited_filename = edited_filename or self._get_edited_filename(
|
||||||
if not edited:
|
original_filename
|
||||||
edited_filename = self.photo.original_filename
|
)
|
||||||
else:
|
|
||||||
original_name = pathlib.Path(self.photo.original_filename)
|
|
||||||
if self.photo.path_edited:
|
|
||||||
ext = pathlib.Path(self.photo.path_edited).suffix
|
|
||||||
else:
|
|
||||||
uti = (
|
|
||||||
self.photo.uti_edited
|
|
||||||
if edited and self.photo.uti_edited
|
|
||||||
else self.photo.uti
|
|
||||||
)
|
|
||||||
ext = get_preferred_uti_extension(uti)
|
|
||||||
ext = "." + ext
|
|
||||||
edited_filename = original_name.stem + "_edited" + ext
|
|
||||||
dest_edited = pathlib.Path(dest) / edited_filename
|
dest_edited = pathlib.Path(dest) / edited_filename
|
||||||
|
|
||||||
|
# Is there something to convert?
|
||||||
if convert_to_jpeg and self.photo.isphoto:
|
if convert_to_jpeg and self.photo.isphoto:
|
||||||
something_to_convert = False
|
something_to_convert = False
|
||||||
ext = "." + jpeg_ext if jpeg_ext else ".jpeg"
|
ext = "." + jpeg_ext if jpeg_ext else ".jpeg"
|
||||||
@@ -508,42 +497,21 @@ class PhotoExporter:
|
|||||||
else:
|
else:
|
||||||
convert_to_jpeg = False
|
convert_to_jpeg = False
|
||||||
|
|
||||||
# check to see if file exists and if so, add (1), (2), etc until we find one that works
|
# TODO: need to look at this to see what happens if original not being exported but edited exists and already has an increment
|
||||||
# Photos checks the stem and adds (1), (2), etc which avoids collision with sidecars
|
dest_original, increment_file_count = self._validate_dest_path(
|
||||||
# e.g. exporting sidecar for file1.png and file1.jpeg
|
dest_original, increment=increment, update=update, overwrite=overwrite
|
||||||
# if file1.png exists and exporting file1.jpeg,
|
)
|
||||||
# dest will be file1 (1).jpeg even though file1.jpeg doesn't exist to prevent sidecar collision
|
dest_original = pathlib.Path(dest_original)
|
||||||
increment_file_count = 0
|
|
||||||
if increment and not update and not overwrite:
|
|
||||||
dest_original, increment_file_count = increment_filename_with_count(
|
|
||||||
dest_original
|
|
||||||
)
|
|
||||||
dest_original = pathlib.Path(dest_original)
|
|
||||||
|
|
||||||
# if overwrite==False and #increment==False, export should fail if file exists
|
|
||||||
if (
|
|
||||||
dest_original.exists()
|
|
||||||
and export_original
|
|
||||||
and not update
|
|
||||||
and not overwrite
|
|
||||||
and not increment
|
|
||||||
):
|
|
||||||
raise FileExistsError(
|
|
||||||
f"destination exists ({dest_original}); overwrite={overwrite}, increment={increment}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if export_edited:
|
if export_edited:
|
||||||
if increment and not update and not overwrite:
|
dest_edited, increment_file_count = self._validate_dest_path(
|
||||||
dest_edited, increment_file_count = increment_filename_with_count(
|
dest_edited,
|
||||||
dest_edited, increment_file_count
|
increment=increment,
|
||||||
)
|
update=update,
|
||||||
dest_edited = pathlib.Path(dest_edited)
|
overwrite=overwrite,
|
||||||
|
count=increment_file_count,
|
||||||
# if overwrite==False and #increment==False, export should fail if file exists
|
)
|
||||||
if dest_edited.exists() and not update and not overwrite and not increment:
|
dest_edited = pathlib.Path(dest_edited)
|
||||||
raise FileExistsError(
|
|
||||||
f"destination exists ({dest_edited}); overwrite={overwrite}, increment={increment}"
|
|
||||||
)
|
|
||||||
|
|
||||||
self._render_options.filepath = (
|
self._render_options.filepath = (
|
||||||
str(dest_original) if export_original else str(dest_edited)
|
str(dest_original) if export_original else str(dest_edited)
|
||||||
@@ -551,43 +519,23 @@ class PhotoExporter:
|
|||||||
all_results = ExportResults()
|
all_results = ExportResults()
|
||||||
|
|
||||||
if use_photos_export:
|
if use_photos_export:
|
||||||
# TODO: collapse these into a single call (refactor _export_photo_with_photos_export)
|
self._export_photo_with_photos_export(
|
||||||
if original:
|
dest=dest_original if export_original else dest_edited,
|
||||||
self._export_photo_with_photos_export(
|
all_results=all_results,
|
||||||
dest_original,
|
fileutil=fileutil,
|
||||||
all_results,
|
export_db=export_db,
|
||||||
fileutil,
|
use_photokit=use_photokit,
|
||||||
export_db,
|
dry_run=dry_run,
|
||||||
use_photokit=use_photokit,
|
timeout=timeout,
|
||||||
dry_run=dry_run,
|
jpeg_ext=jpeg_ext,
|
||||||
timeout=timeout,
|
touch_file=touch_file,
|
||||||
jpeg_ext=jpeg_ext,
|
update=update,
|
||||||
touch_file=touch_file,
|
overwrite=overwrite,
|
||||||
update=update,
|
live_photo=live_photo,
|
||||||
overwrite=overwrite,
|
edited=export_edited,
|
||||||
live_photo=live_photo,
|
convert_to_jpeg=convert_to_jpeg,
|
||||||
edited=False,
|
jpeg_quality=jpeg_quality,
|
||||||
convert_to_jpeg=convert_to_jpeg,
|
)
|
||||||
jpeg_quality=jpeg_quality,
|
|
||||||
)
|
|
||||||
if edited:
|
|
||||||
self._export_photo_with_photos_export(
|
|
||||||
dest_edited,
|
|
||||||
all_results,
|
|
||||||
fileutil,
|
|
||||||
export_db,
|
|
||||||
use_photokit=use_photokit,
|
|
||||||
dry_run=dry_run,
|
|
||||||
timeout=timeout,
|
|
||||||
jpeg_ext=jpeg_ext,
|
|
||||||
touch_file=touch_file,
|
|
||||||
update=update,
|
|
||||||
overwrite=overwrite,
|
|
||||||
live_photo=live_photo,
|
|
||||||
edited=True,
|
|
||||||
convert_to_jpeg=convert_to_jpeg,
|
|
||||||
jpeg_quality=jpeg_quality,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
# find the source file on disk and export
|
# find the source file on disk and export
|
||||||
# get path to source file and verify it's not None and is valid file
|
# get path to source file and verify it's not None and is valid file
|
||||||
@@ -598,6 +546,7 @@ class PhotoExporter:
|
|||||||
elif not edited and self.photo.path is not None:
|
elif not edited and self.photo.path is not None:
|
||||||
export_src_dest.append((self.photo.path, dest_original))
|
export_src_dest.append((self.photo.path, dest_original))
|
||||||
|
|
||||||
|
# TODO: this for loop not necessary
|
||||||
for src, dest in export_src_dest:
|
for src, dest in export_src_dest:
|
||||||
if not pathlib.Path(src).is_file():
|
if not pathlib.Path(src).is_file():
|
||||||
raise FileNotFoundError(f"{src} does not appear to exist")
|
raise FileNotFoundError(f"{src} does not appear to exist")
|
||||||
@@ -1044,6 +993,49 @@ class PhotoExporter:
|
|||||||
|
|
||||||
return all_results
|
return all_results
|
||||||
|
|
||||||
|
def _get_edited_filename(self, original_filename):
|
||||||
|
"""Return the filename for the exported edited photo
|
||||||
|
(used when filename isn't provided in call to export2)"""
|
||||||
|
# need to get the right extension for edited file
|
||||||
|
original_filename = pathlib.Path(original_filename)
|
||||||
|
if self.photo.path_edited:
|
||||||
|
ext = pathlib.Path(self.photo.path_edited).suffix
|
||||||
|
else:
|
||||||
|
uti = self.photo.uti_edited if self.photo.uti_edited else self.photo.uti
|
||||||
|
ext = get_preferred_uti_extension(uti)
|
||||||
|
ext = "." + ext
|
||||||
|
edited_filename = original_filename.stem + "_edited" + ext
|
||||||
|
return edited_filename
|
||||||
|
|
||||||
|
def _validate_dest_path(self, dest, increment, update, overwrite, count=0):
|
||||||
|
"""If destination exists, add (1), (2), and so on to filename to get a valid destination
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dest (str): Destination path
|
||||||
|
increment (bool): Whether to increment the filename if it already exists
|
||||||
|
update (bool): Whether running in update mode
|
||||||
|
overwrite (bool): Whether running in overwrite mode
|
||||||
|
count: optional counter to start from (if 0, start from 1)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
new dest path (pathlib.Path), increment count (int)
|
||||||
|
"""
|
||||||
|
# check to see if file exists and if so, add (1), (2), etc until we find one that works
|
||||||
|
# Photos checks the stem and adds (1), (2), etc which avoids collision with sidecars
|
||||||
|
# e.g. exporting sidecar for file1.png and file1.jpeg
|
||||||
|
# if file1.png exists and exporting file1.jpeg,
|
||||||
|
# dest will be file1 (1).jpeg even though file1.jpeg doesn't exist to prevent sidecar collision
|
||||||
|
if increment and not update and not overwrite:
|
||||||
|
dest, count = increment_filename_with_count(dest, count=count)
|
||||||
|
dest = pathlib.Path(dest)
|
||||||
|
|
||||||
|
# if overwrite==False and #increment==False, export should fail if file exists
|
||||||
|
if dest.exists() and all([not x for x in [increment, update, overwrite]]):
|
||||||
|
raise FileExistsError(
|
||||||
|
f"destination exists ({dest}); overwrite={overwrite}, increment={increment}"
|
||||||
|
)
|
||||||
|
return dest, count
|
||||||
|
|
||||||
def _export_photo_with_photos_export(
|
def _export_photo_with_photos_export(
|
||||||
self,
|
self,
|
||||||
dest,
|
dest,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import Photos
|
|||||||
import Quartz
|
import Quartz
|
||||||
from Foundation import NSNotificationCenter, NSObject
|
from Foundation import NSNotificationCenter, NSObject
|
||||||
from PyObjCTools import AppHelper
|
from PyObjCTools import AppHelper
|
||||||
|
from wurlitzer import pipes
|
||||||
|
|
||||||
from .fileutil import FileUtil
|
from .fileutil import FileUtil
|
||||||
from .uti import get_preferred_uti_extension
|
from .uti import get_preferred_uti_extension
|
||||||
@@ -495,69 +496,73 @@ class PhotoAsset:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
with objc.autorelease_pool():
|
with objc.autorelease_pool():
|
||||||
filename = (
|
with pipes() as (out, err):
|
||||||
pathlib.Path(filename)
|
filename = (
|
||||||
if filename
|
pathlib.Path(filename)
|
||||||
else pathlib.Path(self.original_filename)
|
if filename
|
||||||
)
|
else pathlib.Path(self.original_filename)
|
||||||
|
)
|
||||||
|
|
||||||
dest = pathlib.Path(dest)
|
dest = pathlib.Path(dest)
|
||||||
if not dest.is_dir():
|
if not dest.is_dir():
|
||||||
raise ValueError("dest must be a valid directory: {dest}")
|
raise ValueError("dest must be a valid directory: {dest}")
|
||||||
|
|
||||||
output_file = None
|
output_file = None
|
||||||
if self.isphoto:
|
if self.isphoto:
|
||||||
# will hold exported image data and needs to be cleaned up at end
|
# will hold exported image data and needs to be cleaned up at end
|
||||||
imagedata = None
|
imagedata = None
|
||||||
if raw:
|
if raw:
|
||||||
# export the raw component
|
# export the raw component
|
||||||
resources = self._resources()
|
resources = self._resources()
|
||||||
for resource in resources:
|
for resource in resources:
|
||||||
if resource.type() == Photos.PHAssetResourceTypeAlternatePhoto:
|
if (
|
||||||
data = self._request_resource_data(resource)
|
resource.type()
|
||||||
ext = pathlib.Path(self.raw_filename).suffix[1:]
|
== Photos.PHAssetResourceTypeAlternatePhoto
|
||||||
break
|
):
|
||||||
|
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:
|
else:
|
||||||
raise PhotoKitExportError(
|
# TODO: if user has selected use RAW as original, this returns the RAW
|
||||||
"Could not get image data for RAW photo"
|
# can get the jpeg with resource.type() == Photos.PHAssetResourceTypePhoto
|
||||||
)
|
imagedata = self._request_image_data(version=version)
|
||||||
else:
|
if not imagedata.image_data:
|
||||||
# TODO: if user has selected use RAW as original, this returns the RAW
|
raise PhotoKitExportError("Could not get image data")
|
||||||
# can get the jpeg with resource.type() == Photos.PHAssetResourceTypePhoto
|
ext = get_preferred_uti_extension(imagedata.uti)
|
||||||
imagedata = self._request_image_data(version=version)
|
data = imagedata.image_data
|
||||||
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}"
|
||||||
|
|
||||||
if not overwrite:
|
if not overwrite:
|
||||||
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(data)
|
fd.write(data)
|
||||||
|
|
||||||
if imagedata:
|
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)
|
||||||
if videodata.asset is None:
|
if videodata.asset is None:
|
||||||
raise PhotoKitExportError("Could not get video for asset")
|
raise PhotoKitExportError("Could not get video for asset")
|
||||||
|
|
||||||
url = videodata.asset.URL()
|
url = videodata.asset.URL()
|
||||||
path = pathlib.Path(NSURL_to_path(url))
|
path = pathlib.Path(NSURL_to_path(url))
|
||||||
if not path.is_file():
|
if not path.is_file():
|
||||||
raise FileNotFoundError("Could not get path to video file")
|
raise FileNotFoundError("Could not get path to video file")
|
||||||
ext = path.suffix
|
ext = path.suffix
|
||||||
output_file = dest / f"{filename.stem}{ext}"
|
output_file = dest / f"{filename.stem}{ext}"
|
||||||
|
|
||||||
if not overwrite:
|
if not overwrite:
|
||||||
output_file = pathlib.Path(increment_filename(output_file))
|
output_file = pathlib.Path(increment_filename(output_file))
|
||||||
|
|
||||||
FileUtil.copy(path, output_file)
|
FileUtil.copy(path, output_file)
|
||||||
|
|
||||||
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
|
||||||
@@ -807,42 +812,46 @@ class VideoAsset(PhotoAsset):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
with objc.autorelease_pool():
|
with objc.autorelease_pool():
|
||||||
if self.slow_mo and version == PHOTOS_VERSION_CURRENT:
|
with pipes() as (out, err):
|
||||||
return [
|
if self.slow_mo and version == PHOTOS_VERSION_CURRENT:
|
||||||
self._export_slow_mo(
|
return [
|
||||||
dest, filename=filename, version=version, overwrite=overwrite
|
self._export_slow_mo(
|
||||||
)
|
dest,
|
||||||
]
|
filename=filename,
|
||||||
|
version=version,
|
||||||
|
overwrite=overwrite,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
filename = (
|
filename = (
|
||||||
pathlib.Path(filename)
|
pathlib.Path(filename)
|
||||||
if filename
|
if filename
|
||||||
else pathlib.Path(self.original_filename)
|
else pathlib.Path(self.original_filename)
|
||||||
)
|
)
|
||||||
|
|
||||||
dest = pathlib.Path(dest)
|
dest = pathlib.Path(dest)
|
||||||
if not dest.is_dir():
|
if not dest.is_dir():
|
||||||
raise ValueError("dest must be a valid directory: {dest}")
|
raise ValueError("dest must be a valid directory: {dest}")
|
||||||
|
|
||||||
output_file = None
|
output_file = None
|
||||||
videodata = self._request_video_data(version=version)
|
videodata = self._request_video_data(version=version)
|
||||||
if videodata.asset is None:
|
if videodata.asset is None:
|
||||||
raise PhotoKitExportError("Could not get video for asset")
|
raise PhotoKitExportError("Could not get video for asset")
|
||||||
|
|
||||||
url = videodata.asset.URL()
|
url = videodata.asset.URL()
|
||||||
path = pathlib.Path(NSURL_to_path(url))
|
path = pathlib.Path(NSURL_to_path(url))
|
||||||
del videodata
|
del videodata
|
||||||
if not path.is_file():
|
if not path.is_file():
|
||||||
raise FileNotFoundError("Could not get path to video file")
|
raise FileNotFoundError("Could not get path to video file")
|
||||||
ext = path.suffix
|
ext = path.suffix
|
||||||
output_file = dest / f"{filename.stem}{ext}"
|
output_file = dest / f"{filename.stem}{ext}"
|
||||||
|
|
||||||
if not overwrite:
|
if not overwrite:
|
||||||
output_file = pathlib.Path(increment_filename(output_file))
|
output_file = pathlib.Path(increment_filename(output_file))
|
||||||
|
|
||||||
FileUtil.copy(path, output_file)
|
FileUtil.copy(path, output_file)
|
||||||
|
|
||||||
return [str(output_file)]
|
return [str(output_file)]
|
||||||
|
|
||||||
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
|
||||||
@@ -1053,64 +1062,69 @@ class LivePhotoAsset(PhotoAsset):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
with objc.autorelease_pool():
|
with objc.autorelease_pool():
|
||||||
filename = (
|
with pipes() as (out, err):
|
||||||
pathlib.Path(filename)
|
filename = (
|
||||||
if filename
|
pathlib.Path(filename)
|
||||||
else pathlib.Path(self.original_filename)
|
if filename
|
||||||
)
|
else pathlib.Path(self.original_filename)
|
||||||
|
|
||||||
dest = pathlib.Path(dest)
|
|
||||||
if not dest.is_dir():
|
|
||||||
raise ValueError("dest must be a valid directory: {dest}")
|
|
||||||
|
|
||||||
request = LivePhotoRequest.alloc().initWithManager_Asset_(
|
|
||||||
self._manager, self.phasset
|
|
||||||
)
|
|
||||||
resources = request.requestLivePhotoResources(version=version)
|
|
||||||
|
|
||||||
video_resource = None
|
|
||||||
photo_resource = None
|
|
||||||
for resource in resources:
|
|
||||||
if resource.type() == Photos.PHAssetResourceTypePairedVideo:
|
|
||||||
video_resource = resource
|
|
||||||
elif resource.type() == Photos.PHAssetMediaTypeImage:
|
|
||||||
photo_resource = resource
|
|
||||||
|
|
||||||
if not video_resource or not photo_resource:
|
|
||||||
raise PhotoKitExportError(
|
|
||||||
"Did not find photo/video resources for live photo"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
photo_ext = get_preferred_uti_extension(
|
dest = pathlib.Path(dest)
|
||||||
photo_resource.uniformTypeIdentifier()
|
if not dest.is_dir():
|
||||||
)
|
raise ValueError("dest must be a valid directory: {dest}")
|
||||||
photo_output_file = dest / f"{filename.stem}.{photo_ext}"
|
|
||||||
video_ext = get_preferred_uti_extension(
|
|
||||||
video_resource.uniformTypeIdentifier()
|
|
||||||
)
|
|
||||||
video_output_file = dest / f"{filename.stem}.{video_ext}"
|
|
||||||
|
|
||||||
if not overwrite:
|
request = LivePhotoRequest.alloc().initWithManager_Asset_(
|
||||||
photo_output_file = pathlib.Path(increment_filename(photo_output_file))
|
self._manager, self.phasset
|
||||||
video_output_file = pathlib.Path(increment_filename(video_output_file))
|
)
|
||||||
|
resources = request.requestLivePhotoResources(version=version)
|
||||||
|
|
||||||
exported = []
|
video_resource = None
|
||||||
if photo:
|
photo_resource = None
|
||||||
data = self._request_resource_data(photo_resource)
|
for resource in resources:
|
||||||
# image_data = self.request_image_data(version=version)
|
if resource.type() == Photos.PHAssetResourceTypePairedVideo:
|
||||||
with open(photo_output_file, "wb") as fd:
|
video_resource = resource
|
||||||
fd.write(data)
|
elif resource.type() == Photos.PHAssetMediaTypeImage:
|
||||||
exported.append(str(photo_output_file))
|
photo_resource = resource
|
||||||
del data
|
|
||||||
if video:
|
|
||||||
data = self._request_resource_data(video_resource)
|
|
||||||
with open(video_output_file, "wb") as fd:
|
|
||||||
fd.write(data)
|
|
||||||
exported.append(str(video_output_file))
|
|
||||||
del data
|
|
||||||
|
|
||||||
request.dealloc()
|
if not video_resource or not photo_resource:
|
||||||
return exported
|
raise PhotoKitExportError(
|
||||||
|
"Did not find photo/video resources for live photo"
|
||||||
|
)
|
||||||
|
|
||||||
|
photo_ext = get_preferred_uti_extension(
|
||||||
|
photo_resource.uniformTypeIdentifier()
|
||||||
|
)
|
||||||
|
photo_output_file = dest / f"{filename.stem}.{photo_ext}"
|
||||||
|
video_ext = get_preferred_uti_extension(
|
||||||
|
video_resource.uniformTypeIdentifier()
|
||||||
|
)
|
||||||
|
video_output_file = dest / f"{filename.stem}.{video_ext}"
|
||||||
|
|
||||||
|
if not overwrite:
|
||||||
|
photo_output_file = pathlib.Path(
|
||||||
|
increment_filename(photo_output_file)
|
||||||
|
)
|
||||||
|
video_output_file = pathlib.Path(
|
||||||
|
increment_filename(video_output_file)
|
||||||
|
)
|
||||||
|
|
||||||
|
exported = []
|
||||||
|
if photo:
|
||||||
|
data = self._request_resource_data(photo_resource)
|
||||||
|
# image_data = self.request_image_data(version=version)
|
||||||
|
with open(photo_output_file, "wb") as fd:
|
||||||
|
fd.write(data)
|
||||||
|
exported.append(str(photo_output_file))
|
||||||
|
del data
|
||||||
|
if video:
|
||||||
|
data = self._request_resource_data(video_resource)
|
||||||
|
with open(video_output_file, "wb") as fd:
|
||||||
|
fd.write(data)
|
||||||
|
exported.append(str(video_output_file))
|
||||||
|
del data
|
||||||
|
|
||||||
|
request.dealloc()
|
||||||
|
return exported
|
||||||
|
|
||||||
|
|
||||||
class PhotoLibrary:
|
class PhotoLibrary:
|
||||||
|
|||||||
Reference in New Issue
Block a user