Pass dest_path to template function via RenderOptions, enable implementation of #496

This commit is contained in:
Rhet Turnbull 2021-07-18 19:42:01 -07:00
parent 4f17c8fb23
commit 2d899ef045
6 changed files with 352 additions and 295 deletions

View File

@ -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|

View File

@ -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)

View File

@ -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))

View File

@ -1,3 +1,3 @@
""" version info """
__version__ = "0.42.63"
__version__ = "0.42.64"

View File

@ -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)

View File

@ -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: