Pass dest_path to template function via RenderOptions, enable implementation of #496
This commit is contained in:
parent
4f17c8fb23
commit
2d899ef045
@ -1815,7 +1815,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.42.62'
|
||||
{osxphotos_version} The osxphotos version, e.g. '0.42.64'
|
||||
{osxphotos_cmd_line} The full command line used to run osxphotos
|
||||
|
||||
The following substitutions may result in multiple values. Thus if specified for
|
||||
@ -3038,8 +3038,13 @@ Returns a list of [FolderInfo](#FolderInfo) objects representing the sub-folders
|
||||
#### `parent`
|
||||
Returns a [FolderInfo](#FolderInfo) object representing the folder's parent folder or `None` if album is not a in a folder.
|
||||
|
||||
#### `photo_index(photo)`
|
||||
Returns index of photo in album (based on album sort order).
|
||||
|
||||
|
||||
**Note**: FolderInfo and AlbumInfo objects effectively work as a linked list. The children of a folder are contained in `subfolders` and `album_info` and the parent object of both `AlbumInfo` and `FolderInfo` is represented by `parent`. For example:
|
||||
|
||||
|
||||
```pycon
|
||||
>>> import osxphotos
|
||||
>>> photosdb = osxphotos.PhotosDB()
|
||||
@ -3647,7 +3652,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.42.62'|
|
||||
|{osxphotos_version}|The osxphotos version, e.g. '0.42.64'|
|
||||
|{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|
|
||||
|
||||
2
build.sh
2
build.sh
@ -3,7 +3,7 @@
|
||||
# script to help build osxphotos release
|
||||
# this is unique to my own dev setup
|
||||
|
||||
activate osxphotos
|
||||
source venv/bin/activate
|
||||
rm -rf dist; rm -rf build
|
||||
python3 utils/update_readme.py
|
||||
(cd docsrc && make github && make pdf)
|
||||
|
||||
@ -5,11 +5,13 @@ from typing import Optional
|
||||
|
||||
from osxphotos import ExportResults, PhotoInfo
|
||||
from osxphotos.albuminfo import AlbumInfo
|
||||
from osxphotos.phototemplate import RenderOptions
|
||||
from osxphotos.path_utils import sanitize_dirname
|
||||
|
||||
|
||||
def _get_album_sort_order(album: AlbumInfo, photo: PhotoInfo) -> Optional[int]:
|
||||
"""Get the sort order of photo in album
|
||||
|
||||
|
||||
Returns: sort order as int or None if photo not found in album
|
||||
"""
|
||||
# get the album sort order from the album_info
|
||||
@ -25,6 +27,32 @@ def _get_album_sort_order(album: AlbumInfo, photo: PhotoInfo) -> Optional[int]:
|
||||
return sort_order
|
||||
|
||||
|
||||
def album_sequence(photo: PhotoInfo, options: RenderOptions, **kwargs) -> str:
|
||||
"""Call this with {function} template to get album sequence (sort order) when exporting with {folder_album} template
|
||||
|
||||
For example, calling this template function like the following prepends sequence#_ to each exported file if the file is in an album:
|
||||
|
||||
osxphotos export /path/to/export -V --directory "{folder_album}" --filename "{album?{function:examples/album_sort_order.py::album_sequence}_,}{original_name}"
|
||||
|
||||
"""
|
||||
dest_path = options.dest_path
|
||||
if not dest_path:
|
||||
return ""
|
||||
|
||||
album_info = None
|
||||
for album in photo.album_info:
|
||||
# following code is how {folder_album} builds the folder path
|
||||
folder = "/".join(sanitize_dirname(f) for f in album.folder_names)
|
||||
folder += "/" + sanitize_dirname(album.title)
|
||||
if dest_path.endswith(folder):
|
||||
album_info = album
|
||||
break
|
||||
else:
|
||||
# didn't find the album, so skip this file
|
||||
return ""
|
||||
return str(album_info.photo_index(photo))
|
||||
|
||||
|
||||
def album_sort_order(
|
||||
photo: PhotoInfo, results: ExportResults, verbose: callable, **kwargs
|
||||
):
|
||||
@ -92,7 +120,7 @@ def album_sort_order(
|
||||
if sort_order is None:
|
||||
# didn't find the photo, so skip this file
|
||||
return
|
||||
|
||||
|
||||
verbose(f"Sort order for {filepath} in album {album_dir} is {sort_order}")
|
||||
with open(str(filepath) + "_sort_order.txt", "w") as f:
|
||||
f.write(str(sort_order))
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.42.63"
|
||||
__version__ = "0.42.64"
|
||||
|
||||
@ -24,16 +24,16 @@ from .datetime_utils import get_local_tz
|
||||
|
||||
|
||||
def sort_list_by_keys(values, sort_keys):
|
||||
""" Sorts list values by a second list sort_keys
|
||||
"""Sorts list values by a second list sort_keys
|
||||
e.g. given ["a","c","b"], [1, 3, 2], returns ["a", "b", "c"]
|
||||
|
||||
Args:
|
||||
values: a list of values to be sorted
|
||||
sort_keys: a list of keys to sort values by
|
||||
|
||||
|
||||
Returns:
|
||||
list of values, sorted by sort_keys
|
||||
|
||||
|
||||
Raises:
|
||||
ValueError: raised if len(values) != len(sort_keys)
|
||||
"""
|
||||
@ -63,12 +63,12 @@ class AlbumInfoBaseClass:
|
||||
|
||||
@property
|
||||
def uuid(self):
|
||||
""" return uuid of album """
|
||||
"""return uuid of album"""
|
||||
return self._uuid
|
||||
|
||||
@property
|
||||
def creation_date(self):
|
||||
""" return creation date of album """
|
||||
"""return creation date of album"""
|
||||
try:
|
||||
return self._creation_date
|
||||
except AttributeError:
|
||||
@ -90,8 +90,8 @@ class AlbumInfoBaseClass:
|
||||
|
||||
@property
|
||||
def start_date(self):
|
||||
""" For Albums, return start date (earliest image) of album or None for albums with no images
|
||||
For Import Sessions, return start date of import session (when import began) """
|
||||
"""For Albums, return start date (earliest image) of album or None for albums with no images
|
||||
For Import Sessions, return start date of import session (when import began)"""
|
||||
try:
|
||||
return self._start_date
|
||||
except AttributeError:
|
||||
@ -109,8 +109,8 @@ class AlbumInfoBaseClass:
|
||||
|
||||
@property
|
||||
def end_date(self):
|
||||
""" For Albums, return end date (most recent image) of album or None for albums with no images
|
||||
For Import Sessions, return end date of import sessions (when import was completed) """
|
||||
"""For Albums, return end date (most recent image) of album or None for albums with no images
|
||||
For Import Sessions, return end date of import sessions (when import was completed)"""
|
||||
try:
|
||||
return self._end_date
|
||||
except AttributeError:
|
||||
@ -131,7 +131,7 @@ class AlbumInfoBaseClass:
|
||||
return []
|
||||
|
||||
def __len__(self):
|
||||
""" return number of photos contained in album """
|
||||
"""return number of photos contained in album"""
|
||||
return len(self.photos)
|
||||
|
||||
|
||||
@ -144,12 +144,12 @@ class AlbumInfo(AlbumInfoBaseClass):
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
""" return title / name of album """
|
||||
"""return title / name of album"""
|
||||
return self._title
|
||||
|
||||
@property
|
||||
def photos(self):
|
||||
""" return list of photos contained in album sorted in same sort order as Photos """
|
||||
"""return list of photos contained in album sorted in same sort order as Photos"""
|
||||
try:
|
||||
return self._photos
|
||||
except AttributeError:
|
||||
@ -163,10 +163,10 @@ class AlbumInfo(AlbumInfoBaseClass):
|
||||
|
||||
@property
|
||||
def folder_names(self):
|
||||
""" return hierarchical list of folders the album is contained in
|
||||
the folder list is in form:
|
||||
["Top level folder", "sub folder 1", "sub folder 2", ...]
|
||||
returns empty list if album is not in any folders """
|
||||
"""return hierarchical list of folders the album is contained in
|
||||
the folder list is in form:
|
||||
["Top level folder", "sub folder 1", "sub folder 2", ...]
|
||||
returns empty list if album is not in any folders"""
|
||||
|
||||
try:
|
||||
return self._folder_names
|
||||
@ -176,10 +176,10 @@ class AlbumInfo(AlbumInfoBaseClass):
|
||||
|
||||
@property
|
||||
def folder_list(self):
|
||||
""" return hierarchical list of folders the album is contained in
|
||||
as list of FolderInfo objects in form
|
||||
["Top level folder", "sub folder 1", "sub folder 2", ...]
|
||||
returns empty list if album is not in any folders """
|
||||
"""return hierarchical list of folders the album is contained in
|
||||
as list of FolderInfo objects in form
|
||||
["Top level folder", "sub folder 1", "sub folder 2", ...]
|
||||
returns empty list if album is not in any folders"""
|
||||
|
||||
try:
|
||||
return self._folders
|
||||
@ -189,7 +189,7 @@ class AlbumInfo(AlbumInfoBaseClass):
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
""" returns FolderInfo object for parent folder or None if no parent (e.g. top-level album) """
|
||||
"""returns FolderInfo object for parent folder or None if no parent (e.g. top-level album)"""
|
||||
try:
|
||||
return self._parent
|
||||
except AttributeError:
|
||||
@ -209,11 +209,23 @@ class AlbumInfo(AlbumInfoBaseClass):
|
||||
)
|
||||
return self._parent
|
||||
|
||||
def photo_index(self, photo):
|
||||
"""return index of photo in album (based on album sort order)"""
|
||||
index = 0
|
||||
for p in self.photos:
|
||||
if p.uuid == photo.uuid:
|
||||
return index
|
||||
index += 1
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Photo with uuid {photo.uuid} does not appear to be in this album"
|
||||
)
|
||||
|
||||
|
||||
class ImportInfo(AlbumInfoBaseClass):
|
||||
@property
|
||||
def photos(self):
|
||||
""" return list of photos contained in import session """
|
||||
"""return list of photos contained in import session"""
|
||||
try:
|
||||
return self._photos
|
||||
except AttributeError:
|
||||
@ -231,7 +243,7 @@ class ImportInfo(AlbumInfoBaseClass):
|
||||
|
||||
class FolderInfo:
|
||||
"""
|
||||
Info about a specific folder, contains all the details about the folder
|
||||
Info about a specific folder, contains all the details about the folder
|
||||
including folders, albums, etc
|
||||
"""
|
||||
|
||||
@ -247,17 +259,17 @@ class FolderInfo:
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
""" return title / name of folder"""
|
||||
"""return title / name of folder"""
|
||||
return self._title
|
||||
|
||||
@property
|
||||
def uuid(self):
|
||||
""" return uuid of folder """
|
||||
"""return uuid of folder"""
|
||||
return self._uuid
|
||||
|
||||
@property
|
||||
def album_info(self):
|
||||
""" return list of albums (as AlbumInfo objects) contained in the folder """
|
||||
"""return list of albums (as AlbumInfo objects) contained in the folder"""
|
||||
try:
|
||||
return self._albums
|
||||
except AttributeError:
|
||||
@ -282,7 +294,7 @@ class FolderInfo:
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
""" returns FolderInfo object for parent or None if no parent (e.g. top-level folder) """
|
||||
"""returns FolderInfo object for parent or None if no parent (e.g. top-level folder)"""
|
||||
try:
|
||||
return self._parent
|
||||
except AttributeError:
|
||||
@ -304,7 +316,7 @@ class FolderInfo:
|
||||
|
||||
@property
|
||||
def subfolders(self):
|
||||
""" return list of folders (as FolderInfo objects) contained in the folder """
|
||||
"""return list of folders (as FolderInfo objects) contained in the folder"""
|
||||
try:
|
||||
return self._folders
|
||||
except AttributeError:
|
||||
@ -328,5 +340,5 @@ class FolderInfo:
|
||||
return self._folders
|
||||
|
||||
def __len__(self):
|
||||
""" returns count of folders + albums contained in the folder """
|
||||
"""returns count of folders + albums contained in the folder"""
|
||||
return len(self.subfolders) + len(self.album_info)
|
||||
|
||||
532
osxphotos/cli.py
532
osxphotos/cli.py
@ -2556,133 +2556,52 @@ def export_photo(
|
||||
)
|
||||
|
||||
results = ExportResults()
|
||||
filenames = get_filenames_from_template(
|
||||
photo, filename_template, original_name, strip=strip
|
||||
dest_paths = get_dirnames_from_template(
|
||||
photo, directory, export_by_date, dest, dry_run, strip=strip, edited=False
|
||||
)
|
||||
for filename in filenames:
|
||||
original_filename = pathlib.Path(filename)
|
||||
file_ext = original_filename.suffix
|
||||
if photo.isphoto and (jpeg_ext or convert_to_jpeg):
|
||||
# change the file extension to correct jpeg extension if needed
|
||||
file_ext = (
|
||||
"." + jpeg_ext
|
||||
if jpeg_ext and (photo.uti_original == "public.jpeg" or convert_to_jpeg)
|
||||
else ".jpeg"
|
||||
if convert_to_jpeg and photo.uti_original != "public.jpeg"
|
||||
else original_filename.suffix
|
||||
)
|
||||
original_filename = (
|
||||
original_filename.parent
|
||||
/ f"{original_filename.stem}{rendered_suffix}{file_ext}"
|
||||
)
|
||||
original_filename = str(original_filename)
|
||||
|
||||
verbose_(
|
||||
f"Exporting {photo.original_filename} ({photo.filename}) as {original_filename}"
|
||||
for dest_path in dest_paths:
|
||||
filenames = get_filenames_from_template(
|
||||
photo, filename_template, dest, dest_path, original_name, strip=strip
|
||||
)
|
||||
|
||||
results += export_photo_with_template(
|
||||
photo=photo,
|
||||
filename=original_filename,
|
||||
directory=directory,
|
||||
edited=False,
|
||||
use_photos_export=use_photos_export,
|
||||
export_by_date=export_by_date,
|
||||
dest=dest,
|
||||
dry_run=dry_run,
|
||||
strip=strip,
|
||||
export_original=export_original,
|
||||
missing=missing_original,
|
||||
verbose=verbose,
|
||||
sidecar_flags=sidecar_flags,
|
||||
sidecar_drop_ext=sidecar_drop_ext,
|
||||
export_live=export_live,
|
||||
export_raw=export_raw,
|
||||
export_as_hardlink=export_as_hardlink,
|
||||
overwrite=overwrite,
|
||||
exiftool=exiftool,
|
||||
exiftool_merge_keywords=exiftool_merge_keywords,
|
||||
exiftool_merge_persons=exiftool_merge_persons,
|
||||
album_keyword=album_keyword,
|
||||
person_keyword=person_keyword,
|
||||
keyword_template=keyword_template,
|
||||
description_template=description_template,
|
||||
update=update,
|
||||
ignore_signature=ignore_signature,
|
||||
export_db=export_db,
|
||||
fileutil=fileutil,
|
||||
touch_file=touch_file,
|
||||
convert_to_jpeg=convert_to_jpeg,
|
||||
jpeg_quality=jpeg_quality,
|
||||
ignore_date_modified=ignore_date_modified,
|
||||
use_photokit=use_photokit,
|
||||
exiftool_option=exiftool_option,
|
||||
jpeg_ext=jpeg_ext,
|
||||
replace_keywords=replace_keywords,
|
||||
retry=retry,
|
||||
export_dir=export_dir,
|
||||
export_preview=export_preview,
|
||||
preview_suffix=rendered_preview_suffix,
|
||||
preview_if_missing=preview_if_missing,
|
||||
)
|
||||
|
||||
if export_edited and photo.hasadjustments:
|
||||
# if export-edited, also export the edited version
|
||||
edited_filenames = get_filenames_from_template(
|
||||
photo, filename_template, original_name, strip=strip, edited=True
|
||||
)
|
||||
for edited_filename in edited_filenames:
|
||||
edited_filename = pathlib.Path(edited_filename)
|
||||
# verify the photo has adjustments and valid path to avoid raising an exception
|
||||
edited_ext = (
|
||||
# rare cases on Photos <= 4 that uti_edited is None
|
||||
"." + get_preferred_uti_extension(photo.uti_edited)
|
||||
if photo.uti_edited
|
||||
else pathlib.Path(photo.path_edited).suffix
|
||||
if photo.path_edited
|
||||
else pathlib.Path(photo.filename).suffix
|
||||
)
|
||||
|
||||
if photo.isphoto and jpeg_ext and edited_ext.lower() in [".jpg", ".jpeg"]:
|
||||
edited_ext = "." + jpeg_ext
|
||||
|
||||
# Big Sur uses .heic for some edited photos so need to check
|
||||
# if extension isn't jpeg/jpg and using --convert-to-jpeg
|
||||
if (
|
||||
photo.isphoto
|
||||
and convert_to_jpeg
|
||||
and edited_ext.lower() not in [".jpg", ".jpeg"]
|
||||
):
|
||||
edited_ext = "." + jpeg_ext if jpeg_ext else ".jpeg"
|
||||
|
||||
rendered_edited_suffix = _render_suffix_template(
|
||||
edited_suffix, "edited_suffix", "--edited-suffix", strip, dest, photo
|
||||
)
|
||||
edited_filename = (
|
||||
f"{edited_filename.stem}{rendered_edited_suffix}{edited_ext}"
|
||||
for filename in filenames:
|
||||
original_filename = pathlib.Path(filename)
|
||||
file_ext = original_filename.suffix
|
||||
if photo.isphoto and (jpeg_ext or convert_to_jpeg):
|
||||
# change the file extension to correct jpeg extension if needed
|
||||
file_ext = (
|
||||
"." + jpeg_ext
|
||||
if jpeg_ext
|
||||
and (photo.uti_original == "public.jpeg" or convert_to_jpeg)
|
||||
else ".jpeg"
|
||||
if convert_to_jpeg and photo.uti_original != "public.jpeg"
|
||||
else original_filename.suffix
|
||||
)
|
||||
original_filename = (
|
||||
original_filename.parent
|
||||
/ f"{original_filename.stem}{rendered_suffix}{file_ext}"
|
||||
)
|
||||
original_filename = str(original_filename)
|
||||
|
||||
verbose_(
|
||||
f"Exporting edited version of {photo.original_filename} ({photo.filename}) as {edited_filename}"
|
||||
f"Exporting {photo.original_filename} ({photo.filename}) as {original_filename}"
|
||||
)
|
||||
|
||||
results += export_photo_with_template(
|
||||
results += export_photo_to_directory(
|
||||
photo=photo,
|
||||
filename=edited_filename,
|
||||
directory=directory,
|
||||
edited=True,
|
||||
filename=original_filename,
|
||||
dest_path=dest_path,
|
||||
edited=False,
|
||||
use_photos_export=use_photos_export,
|
||||
export_by_date=export_by_date,
|
||||
dest=dest,
|
||||
dry_run=dry_run,
|
||||
strip=strip,
|
||||
export_original=False,
|
||||
missing=missing_edited,
|
||||
export_original=export_original,
|
||||
missing=missing_original,
|
||||
verbose=verbose,
|
||||
sidecar_flags=sidecar_flags if not export_original else 0,
|
||||
sidecar_flags=sidecar_flags,
|
||||
sidecar_drop_ext=sidecar_drop_ext,
|
||||
export_live=export_live,
|
||||
export_raw=not export_original and export_raw,
|
||||
export_raw=export_raw,
|
||||
export_as_hardlink=export_as_hardlink,
|
||||
overwrite=overwrite,
|
||||
exiftool=exiftool,
|
||||
@ -2706,11 +2625,107 @@ def export_photo(
|
||||
replace_keywords=replace_keywords,
|
||||
retry=retry,
|
||||
export_dir=export_dir,
|
||||
export_preview=not export_original and export_preview,
|
||||
export_preview=export_preview,
|
||||
preview_suffix=rendered_preview_suffix,
|
||||
preview_if_missing=preview_if_missing,
|
||||
)
|
||||
|
||||
if export_edited and photo.hasadjustments:
|
||||
dest_paths = get_dirnames_from_template(
|
||||
photo, directory, export_by_date, dest, dry_run, strip=strip, edited=True
|
||||
)
|
||||
for dest_path in dest_paths:
|
||||
# if export-edited, also export the edited version
|
||||
edited_filenames = get_filenames_from_template(
|
||||
photo, filename_template, dest, dest_path, original_name, strip=strip, edited=True
|
||||
)
|
||||
for edited_filename in edited_filenames:
|
||||
edited_filename = pathlib.Path(edited_filename)
|
||||
# verify the photo has adjustments and valid path to avoid raising an exception
|
||||
edited_ext = (
|
||||
# rare cases on Photos <= 4 that uti_edited is None
|
||||
"." + get_preferred_uti_extension(photo.uti_edited)
|
||||
if photo.uti_edited
|
||||
else pathlib.Path(photo.path_edited).suffix
|
||||
if photo.path_edited
|
||||
else pathlib.Path(photo.filename).suffix
|
||||
)
|
||||
|
||||
if (
|
||||
photo.isphoto
|
||||
and jpeg_ext
|
||||
and edited_ext.lower() in [".jpg", ".jpeg"]
|
||||
):
|
||||
edited_ext = "." + jpeg_ext
|
||||
|
||||
# Big Sur uses .heic for some edited photos so need to check
|
||||
# if extension isn't jpeg/jpg and using --convert-to-jpeg
|
||||
if (
|
||||
photo.isphoto
|
||||
and convert_to_jpeg
|
||||
and edited_ext.lower() not in [".jpg", ".jpeg"]
|
||||
):
|
||||
edited_ext = "." + jpeg_ext if jpeg_ext else ".jpeg"
|
||||
|
||||
rendered_edited_suffix = _render_suffix_template(
|
||||
edited_suffix,
|
||||
"edited_suffix",
|
||||
"--edited-suffix",
|
||||
strip,
|
||||
dest,
|
||||
photo,
|
||||
)
|
||||
edited_filename = (
|
||||
f"{edited_filename.stem}{rendered_edited_suffix}{edited_ext}"
|
||||
)
|
||||
|
||||
verbose_(
|
||||
f"Exporting edited version of {photo.original_filename} ({photo.filename}) as {edited_filename}"
|
||||
)
|
||||
|
||||
results += export_photo_to_directory(
|
||||
photo=photo,
|
||||
filename=edited_filename,
|
||||
dest_path=dest_path,
|
||||
edited=True,
|
||||
use_photos_export=use_photos_export,
|
||||
dest=dest,
|
||||
dry_run=dry_run,
|
||||
export_original=False,
|
||||
missing=missing_edited,
|
||||
verbose=verbose,
|
||||
sidecar_flags=sidecar_flags if not export_original else 0,
|
||||
sidecar_drop_ext=sidecar_drop_ext,
|
||||
export_live=export_live,
|
||||
export_raw=not export_original and export_raw,
|
||||
export_as_hardlink=export_as_hardlink,
|
||||
overwrite=overwrite,
|
||||
exiftool=exiftool,
|
||||
exiftool_merge_keywords=exiftool_merge_keywords,
|
||||
exiftool_merge_persons=exiftool_merge_persons,
|
||||
album_keyword=album_keyword,
|
||||
person_keyword=person_keyword,
|
||||
keyword_template=keyword_template,
|
||||
description_template=description_template,
|
||||
update=update,
|
||||
ignore_signature=ignore_signature,
|
||||
export_db=export_db,
|
||||
fileutil=fileutil,
|
||||
touch_file=touch_file,
|
||||
convert_to_jpeg=convert_to_jpeg,
|
||||
jpeg_quality=jpeg_quality,
|
||||
ignore_date_modified=ignore_date_modified,
|
||||
use_photokit=use_photokit,
|
||||
exiftool_option=exiftool_option,
|
||||
jpeg_ext=jpeg_ext,
|
||||
replace_keywords=replace_keywords,
|
||||
retry=retry,
|
||||
export_dir=export_dir,
|
||||
export_preview=not export_original and export_preview,
|
||||
preview_suffix=rendered_preview_suffix,
|
||||
preview_if_missing=preview_if_missing,
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@ -2744,16 +2759,14 @@ def _render_suffix_template(suffix_template, var_name, option_name, strip, dest,
|
||||
return rendered_suffix[0]
|
||||
|
||||
|
||||
def export_photo_with_template(
|
||||
def export_photo_to_directory(
|
||||
photo,
|
||||
filename,
|
||||
directory,
|
||||
dest_path,
|
||||
edited,
|
||||
use_photos_export,
|
||||
export_by_date,
|
||||
dest,
|
||||
dry_run,
|
||||
strip,
|
||||
export_original,
|
||||
missing,
|
||||
verbose,
|
||||
@ -2788,159 +2801,153 @@ def export_photo_with_template(
|
||||
preview_suffix,
|
||||
preview_if_missing,
|
||||
):
|
||||
"""Evaluate directory template then export photo to each directory"""
|
||||
"""Export photo to directory dest_path"""
|
||||
|
||||
results = ExportResults()
|
||||
if export_original:
|
||||
if missing and not preview_if_missing:
|
||||
space = " " if not verbose else ""
|
||||
verbose_(
|
||||
f"{space}Skipping missing photo {photo.original_filename} ({photo.uuid})"
|
||||
)
|
||||
results.missing.append(str(pathlib.Path(dest_path) / filename))
|
||||
elif (
|
||||
photo.intrash
|
||||
and (not photo.path or use_photos_export)
|
||||
and not preview_if_missing
|
||||
):
|
||||
# skip deleted files if they're missing or using use_photos_export
|
||||
# as AppleScript/PhotoKit cannot export deleted photos
|
||||
space = " " if not verbose else ""
|
||||
verbose_(
|
||||
f"{space}Skipping missing deleted photo {photo.original_filename} ({photo.uuid})"
|
||||
)
|
||||
results.missing.append(str(pathlib.Path(dest_path) / filename))
|
||||
return results
|
||||
elif not edited:
|
||||
verbose_(f"Skipping original version of {photo.original_filename}")
|
||||
return results
|
||||
else:
|
||||
# exporting the edited version
|
||||
if missing and not preview_if_missing:
|
||||
space = " " if not verbose else ""
|
||||
verbose_(f"{space}Skipping missing edited photo for {filename}")
|
||||
results.missing.append(str(pathlib.Path(dest_path) / filename))
|
||||
return results
|
||||
elif (
|
||||
photo.intrash
|
||||
and (not photo.path_edited or use_photos_export)
|
||||
and not preview_if_missing
|
||||
):
|
||||
# skip deleted files if they're missing or using use_photos_export
|
||||
# as AppleScript/PhotoKit cannot export deleted photos
|
||||
space = " " if not verbose else ""
|
||||
verbose_(
|
||||
f"{space}Skipping missing deleted photo {photo.original_filename} ({photo.uuid})"
|
||||
)
|
||||
results.missing.append(str(pathlib.Path(dest_path) / filename))
|
||||
return results
|
||||
|
||||
dest_paths = get_dirnames_from_template(
|
||||
photo, directory, export_by_date, dest, dry_run, strip=strip, edited=edited
|
||||
)
|
||||
render_options = RenderOptions(export_dir=export_dir, dest_path=dest_path)
|
||||
|
||||
# export the photo to each path in dest_paths
|
||||
for dest_path in dest_paths:
|
||||
if export_original:
|
||||
if missing and not preview_if_missing:
|
||||
space = " " if not verbose else ""
|
||||
verbose_(
|
||||
f"{space}Skipping missing photo {photo.original_filename} ({photo.uuid})"
|
||||
)
|
||||
results.missing.append(str(pathlib.Path(dest_path) / filename))
|
||||
elif (
|
||||
photo.intrash
|
||||
and (not photo.path or use_photos_export)
|
||||
and not preview_if_missing
|
||||
):
|
||||
# skip deleted files if they're missing or using use_photos_export
|
||||
# as AppleScript/PhotoKit cannot export deleted photos
|
||||
space = " " if not verbose else ""
|
||||
verbose_(
|
||||
f"{space}Skipping missing deleted photo {photo.original_filename} ({photo.uuid})"
|
||||
)
|
||||
results.missing.append(str(pathlib.Path(dest_path) / filename))
|
||||
continue
|
||||
elif not edited:
|
||||
verbose_(f"Skipping original version of {photo.original_filename}")
|
||||
continue
|
||||
else:
|
||||
# exporting the edited version
|
||||
if missing and not preview_if_missing:
|
||||
space = " " if not verbose else ""
|
||||
verbose_(f"{space}Skipping missing edited photo for {filename}")
|
||||
results.missing.append(str(pathlib.Path(dest_path) / filename))
|
||||
continue
|
||||
elif (
|
||||
photo.intrash
|
||||
and (not photo.path_edited or use_photos_export)
|
||||
and not preview_if_missing
|
||||
):
|
||||
# skip deleted files if they're missing or using use_photos_export
|
||||
# as AppleScript/PhotoKit cannot export deleted photos
|
||||
space = " " if not verbose else ""
|
||||
verbose_(
|
||||
f"{space}Skipping missing deleted photo {photo.original_filename} ({photo.uuid})"
|
||||
)
|
||||
results.missing.append(str(pathlib.Path(dest_path) / filename))
|
||||
continue
|
||||
|
||||
render_options = RenderOptions(export_dir=export_dir, dest_path=dest_path)
|
||||
|
||||
tries = 0
|
||||
while tries <= retry:
|
||||
tries += 1
|
||||
error = 0
|
||||
try:
|
||||
export_results = photo.export2(
|
||||
dest_path,
|
||||
original_filename=filename,
|
||||
edited=edited,
|
||||
original=export_original,
|
||||
edited_filename=filename,
|
||||
sidecar=sidecar_flags,
|
||||
sidecar_drop_ext=sidecar_drop_ext,
|
||||
live_photo=export_live,
|
||||
raw_photo=export_raw,
|
||||
export_as_hardlink=export_as_hardlink,
|
||||
overwrite=overwrite,
|
||||
use_photos_export=use_photos_export,
|
||||
exiftool=exiftool,
|
||||
merge_exif_keywords=exiftool_merge_keywords,
|
||||
merge_exif_persons=exiftool_merge_persons,
|
||||
use_albums_as_keywords=album_keyword,
|
||||
use_persons_as_keywords=person_keyword,
|
||||
keyword_template=keyword_template,
|
||||
description_template=description_template,
|
||||
update=update,
|
||||
ignore_signature=ignore_signature,
|
||||
export_db=export_db,
|
||||
fileutil=fileutil,
|
||||
dry_run=dry_run,
|
||||
touch_file=touch_file,
|
||||
convert_to_jpeg=convert_to_jpeg,
|
||||
jpeg_quality=jpeg_quality,
|
||||
ignore_date_modified=ignore_date_modified,
|
||||
use_photokit=use_photokit,
|
||||
verbose=verbose_,
|
||||
exiftool_flags=exiftool_option,
|
||||
jpeg_ext=jpeg_ext,
|
||||
replace_keywords=replace_keywords,
|
||||
render_options=render_options,
|
||||
preview=export_preview or (missing and preview_if_missing),
|
||||
preview_suffix=preview_suffix,
|
||||
)
|
||||
for warning_ in export_results.exiftool_warning:
|
||||
verbose_(f"exiftool warning for file {warning_[0]}: {warning_[1]}")
|
||||
for error_ in export_results.exiftool_error:
|
||||
click.echo(
|
||||
click.style(
|
||||
f"exiftool error for file {error_[0]}: {error_[1]}",
|
||||
fg=CLI_COLOR_ERROR,
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
for error_ in export_results.error:
|
||||
click.echo(
|
||||
click.style(
|
||||
f"Error exporting photo ({photo.uuid}: {photo.original_filename}) as {error_[0]}: {error_[1]}",
|
||||
fg=CLI_COLOR_ERROR,
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
error += 1
|
||||
if not error or tries > retry:
|
||||
results += export_results
|
||||
break
|
||||
else:
|
||||
click.echo(
|
||||
"Retrying export for photo ({photo.uuid}: {photo.original_filename})"
|
||||
)
|
||||
except Exception as e:
|
||||
tries = 0
|
||||
while tries <= retry:
|
||||
tries += 1
|
||||
error = 0
|
||||
try:
|
||||
export_results = photo.export2(
|
||||
dest_path,
|
||||
original_filename=filename,
|
||||
edited=edited,
|
||||
original=export_original,
|
||||
edited_filename=filename,
|
||||
sidecar=sidecar_flags,
|
||||
sidecar_drop_ext=sidecar_drop_ext,
|
||||
live_photo=export_live,
|
||||
raw_photo=export_raw,
|
||||
export_as_hardlink=export_as_hardlink,
|
||||
overwrite=overwrite,
|
||||
use_photos_export=use_photos_export,
|
||||
exiftool=exiftool,
|
||||
merge_exif_keywords=exiftool_merge_keywords,
|
||||
merge_exif_persons=exiftool_merge_persons,
|
||||
use_albums_as_keywords=album_keyword,
|
||||
use_persons_as_keywords=person_keyword,
|
||||
keyword_template=keyword_template,
|
||||
description_template=description_template,
|
||||
update=update,
|
||||
ignore_signature=ignore_signature,
|
||||
export_db=export_db,
|
||||
fileutil=fileutil,
|
||||
dry_run=dry_run,
|
||||
touch_file=touch_file,
|
||||
convert_to_jpeg=convert_to_jpeg,
|
||||
jpeg_quality=jpeg_quality,
|
||||
ignore_date_modified=ignore_date_modified,
|
||||
use_photokit=use_photokit,
|
||||
verbose=verbose_,
|
||||
exiftool_flags=exiftool_option,
|
||||
jpeg_ext=jpeg_ext,
|
||||
replace_keywords=replace_keywords,
|
||||
render_options=render_options,
|
||||
preview=export_preview or (missing and preview_if_missing),
|
||||
preview_suffix=preview_suffix,
|
||||
)
|
||||
for warning_ in export_results.exiftool_warning:
|
||||
verbose_(f"exiftool warning for file {warning_[0]}: {warning_[1]}")
|
||||
for error_ in export_results.exiftool_error:
|
||||
click.echo(
|
||||
click.style(
|
||||
f"Error exporting photo ({photo.uuid}: {photo.original_filename}) as {filename}: {e}",
|
||||
f"exiftool error for file {error_[0]}: {error_[1]}",
|
||||
fg=CLI_COLOR_ERROR,
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
if tries > retry:
|
||||
results.error.append((str(pathlib.Path(dest) / filename), e))
|
||||
break
|
||||
else:
|
||||
click.echo(
|
||||
f"Retrying export for photo ({photo.uuid}: {photo.original_filename})"
|
||||
)
|
||||
|
||||
if verbose:
|
||||
if update:
|
||||
for new in results.new:
|
||||
verbose_(f"Exported new file {new}")
|
||||
for updated in results.updated:
|
||||
verbose_(f"Exported updated file {updated}")
|
||||
for skipped in results.skipped:
|
||||
verbose_(f"Skipped up to date file {skipped}")
|
||||
for error_ in export_results.error:
|
||||
click.echo(
|
||||
click.style(
|
||||
f"Error exporting photo ({photo.uuid}: {photo.original_filename}) as {error_[0]}: {error_[1]}",
|
||||
fg=CLI_COLOR_ERROR,
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
error += 1
|
||||
if not error or tries > retry:
|
||||
results += export_results
|
||||
break
|
||||
else:
|
||||
for exported in results.exported:
|
||||
verbose_(f"Exported {exported}")
|
||||
for touched in results.touched:
|
||||
verbose_(f"Touched date on file {touched}")
|
||||
click.echo(
|
||||
"Retrying export for photo ({photo.uuid}: {photo.original_filename})"
|
||||
)
|
||||
except Exception as e:
|
||||
click.echo(
|
||||
click.style(
|
||||
f"Error exporting photo ({photo.uuid}: {photo.original_filename}) as {filename}: {e}",
|
||||
fg=CLI_COLOR_ERROR,
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
if tries > retry:
|
||||
results.error.append((str(pathlib.Path(dest) / filename), e))
|
||||
break
|
||||
else:
|
||||
click.echo(
|
||||
f"Retrying export for photo ({photo.uuid}: {photo.original_filename})"
|
||||
)
|
||||
|
||||
if verbose:
|
||||
if update:
|
||||
for new in results.new:
|
||||
verbose_(f"Exported new file {new}")
|
||||
for updated in results.updated:
|
||||
verbose_(f"Exported updated file {updated}")
|
||||
for skipped in results.skipped:
|
||||
verbose_(f"Skipped up to date file {skipped}")
|
||||
else:
|
||||
for exported in results.exported:
|
||||
verbose_(f"Exported {exported}")
|
||||
for touched in results.touched:
|
||||
verbose_(f"Touched date on file {touched}")
|
||||
|
||||
return results
|
||||
|
||||
@ -2948,6 +2955,8 @@ def export_photo_with_template(
|
||||
def get_filenames_from_template(
|
||||
photo,
|
||||
filename_template,
|
||||
export_dir,
|
||||
dest_path,
|
||||
original_name,
|
||||
strip=False,
|
||||
edited=False,
|
||||
@ -2958,6 +2967,7 @@ def get_filenames_from_template(
|
||||
photo: a PhotoInfo instance
|
||||
filename_template: a PhotoTemplate template string, may be None
|
||||
original_name: boolean; if True, use photo's original filename instead of current filename
|
||||
dest_path: the path the photo will be exported to
|
||||
strip: if True, strips leading/trailing white space from resulting template
|
||||
edited: if True, sets {edited_version} field to True, otherwise it gets set to False; set if you want template evaluated for edited version
|
||||
|
||||
@ -2975,6 +2985,8 @@ def get_filenames_from_template(
|
||||
filename=True,
|
||||
strip=strip,
|
||||
edited_version=edited,
|
||||
export_dir=export_dir,
|
||||
dest_path=dest_path,
|
||||
)
|
||||
filenames, unmatched = photo.render_template(filename_template, options)
|
||||
except ValueError as e:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user