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} {lf} A line feed: '\n', alias for {newline}
{cr} A carriage return: '\r' {cr} A carriage return: '\r'
{crlf} a carriage return + line feed: '\r\n' {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 {osxphotos_cmd_line} The full command line used to run osxphotos
The following substitutions may result in multiple values. Thus if specified for 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` #### `parent`
Returns a [FolderInfo](#FolderInfo) object representing the folder's parent folder or `None` if album is not a in a folder. 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: **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 ```pycon
>>> import osxphotos >>> import osxphotos
>>> photosdb = osxphotos.PhotosDB() >>> 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}| |{lf}|A line feed: '\n', alias for {newline}|
|{cr}|A carriage return: '\r'| |{cr}|A carriage return: '\r'|
|{crlf}|a carriage return + line feed: '\r\n'| |{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| |{osxphotos_cmd_line}|The full command line used to run osxphotos|
|{album}|Album(s) photo is contained in| |{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| |{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 # script to help build osxphotos release
# this is unique to my own dev setup # this is unique to my own dev setup
activate osxphotos source venv/bin/activate
rm -rf dist; rm -rf build rm -rf dist; rm -rf build
python3 utils/update_readme.py python3 utils/update_readme.py
(cd docsrc && make github && make pdf) (cd docsrc && make github && make pdf)

View File

@@ -5,6 +5,8 @@ from typing import Optional
from osxphotos import ExportResults, PhotoInfo from osxphotos import ExportResults, PhotoInfo
from osxphotos.albuminfo import AlbumInfo 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]: def _get_album_sort_order(album: AlbumInfo, photo: PhotoInfo) -> Optional[int]:
@@ -25,6 +27,32 @@ def _get_album_sort_order(album: AlbumInfo, photo: PhotoInfo) -> Optional[int]:
return sort_order 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( def album_sort_order(
photo: PhotoInfo, results: ExportResults, verbose: callable, **kwargs photo: PhotoInfo, results: ExportResults, verbose: callable, **kwargs
): ):

View File

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

View File

@@ -209,6 +209,18 @@ class AlbumInfo(AlbumInfoBaseClass):
) )
return self._parent 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): class ImportInfo(AlbumInfoBaseClass):
@property @property

View File

@@ -2556,9 +2556,14 @@ def export_photo(
) )
results = ExportResults() results = ExportResults()
filenames = get_filenames_from_template( dest_paths = get_dirnames_from_template(
photo, filename_template, original_name, strip=strip photo, directory, export_by_date, dest, dry_run, strip=strip, edited=False
) )
for dest_path in dest_paths:
filenames = get_filenames_from_template(
photo, filename_template, dest, dest_path, original_name, strip=strip
)
for filename in filenames: for filename in filenames:
original_filename = pathlib.Path(filename) original_filename = pathlib.Path(filename)
file_ext = original_filename.suffix file_ext = original_filename.suffix
@@ -2566,7 +2571,8 @@ def export_photo(
# change the file extension to correct jpeg extension if needed # change the file extension to correct jpeg extension if needed
file_ext = ( file_ext = (
"." + jpeg_ext "." + jpeg_ext
if jpeg_ext and (photo.uti_original == "public.jpeg" or convert_to_jpeg) if jpeg_ext
and (photo.uti_original == "public.jpeg" or convert_to_jpeg)
else ".jpeg" else ".jpeg"
if convert_to_jpeg and photo.uti_original != "public.jpeg" if convert_to_jpeg and photo.uti_original != "public.jpeg"
else original_filename.suffix else original_filename.suffix
@@ -2581,16 +2587,14 @@ def export_photo(
f"Exporting {photo.original_filename} ({photo.filename}) as {original_filename}" f"Exporting {photo.original_filename} ({photo.filename}) as {original_filename}"
) )
results += export_photo_with_template( results += export_photo_to_directory(
photo=photo, photo=photo,
filename=original_filename, filename=original_filename,
directory=directory, dest_path=dest_path,
edited=False, edited=False,
use_photos_export=use_photos_export, use_photos_export=use_photos_export,
export_by_date=export_by_date,
dest=dest, dest=dest,
dry_run=dry_run, dry_run=dry_run,
strip=strip,
export_original=export_original, export_original=export_original,
missing=missing_original, missing=missing_original,
verbose=verbose, verbose=verbose,
@@ -2627,9 +2631,13 @@ def export_photo(
) )
if export_edited and photo.hasadjustments: 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 # if export-edited, also export the edited version
edited_filenames = get_filenames_from_template( edited_filenames = get_filenames_from_template(
photo, filename_template, original_name, strip=strip, edited=True photo, filename_template, dest, dest_path, original_name, strip=strip, edited=True
) )
for edited_filename in edited_filenames: for edited_filename in edited_filenames:
edited_filename = pathlib.Path(edited_filename) edited_filename = pathlib.Path(edited_filename)
@@ -2643,7 +2651,11 @@ def export_photo(
else pathlib.Path(photo.filename).suffix else pathlib.Path(photo.filename).suffix
) )
if photo.isphoto and jpeg_ext and edited_ext.lower() in [".jpg", ".jpeg"]: if (
photo.isphoto
and jpeg_ext
and edited_ext.lower() in [".jpg", ".jpeg"]
):
edited_ext = "." + jpeg_ext edited_ext = "." + jpeg_ext
# Big Sur uses .heic for some edited photos so need to check # Big Sur uses .heic for some edited photos so need to check
@@ -2656,7 +2668,12 @@ def export_photo(
edited_ext = "." + jpeg_ext if jpeg_ext else ".jpeg" edited_ext = "." + jpeg_ext if jpeg_ext else ".jpeg"
rendered_edited_suffix = _render_suffix_template( rendered_edited_suffix = _render_suffix_template(
edited_suffix, "edited_suffix", "--edited-suffix", strip, dest, photo edited_suffix,
"edited_suffix",
"--edited-suffix",
strip,
dest,
photo,
) )
edited_filename = ( edited_filename = (
f"{edited_filename.stem}{rendered_edited_suffix}{edited_ext}" f"{edited_filename.stem}{rendered_edited_suffix}{edited_ext}"
@@ -2666,16 +2683,14 @@ def export_photo(
f"Exporting edited version of {photo.original_filename} ({photo.filename}) as {edited_filename}" f"Exporting edited version of {photo.original_filename} ({photo.filename}) as {edited_filename}"
) )
results += export_photo_with_template( results += export_photo_to_directory(
photo=photo, photo=photo,
filename=edited_filename, filename=edited_filename,
directory=directory, dest_path=dest_path,
edited=True, edited=True,
use_photos_export=use_photos_export, use_photos_export=use_photos_export,
export_by_date=export_by_date,
dest=dest, dest=dest,
dry_run=dry_run, dry_run=dry_run,
strip=strip,
export_original=False, export_original=False,
missing=missing_edited, missing=missing_edited,
verbose=verbose, verbose=verbose,
@@ -2744,16 +2759,14 @@ def _render_suffix_template(suffix_template, var_name, option_name, strip, dest,
return rendered_suffix[0] return rendered_suffix[0]
def export_photo_with_template( def export_photo_to_directory(
photo, photo,
filename, filename,
directory, dest_path,
edited, edited,
use_photos_export, use_photos_export,
export_by_date,
dest, dest,
dry_run, dry_run,
strip,
export_original, export_original,
missing, missing,
verbose, verbose,
@@ -2788,15 +2801,9 @@ def export_photo_with_template(
preview_suffix, preview_suffix,
preview_if_missing, preview_if_missing,
): ):
"""Evaluate directory template then export photo to each directory""" """Export photo to directory dest_path"""
results = ExportResults() results = ExportResults()
dest_paths = get_dirnames_from_template(
photo, directory, export_by_date, dest, dry_run, strip=strip, edited=edited
)
# export the photo to each path in dest_paths
for dest_path in dest_paths:
if export_original: if export_original:
if missing and not preview_if_missing: if missing and not preview_if_missing:
space = " " if not verbose else "" space = " " if not verbose else ""
@@ -2816,17 +2823,17 @@ def export_photo_with_template(
f"{space}Skipping missing deleted photo {photo.original_filename} ({photo.uuid})" f"{space}Skipping missing deleted photo {photo.original_filename} ({photo.uuid})"
) )
results.missing.append(str(pathlib.Path(dest_path) / filename)) results.missing.append(str(pathlib.Path(dest_path) / filename))
continue return results
elif not edited: elif not edited:
verbose_(f"Skipping original version of {photo.original_filename}") verbose_(f"Skipping original version of {photo.original_filename}")
continue return results
else: else:
# exporting the edited version # exporting the edited version
if missing and not preview_if_missing: if missing and not preview_if_missing:
space = " " if not verbose else "" space = " " if not verbose else ""
verbose_(f"{space}Skipping missing edited photo for {filename}") verbose_(f"{space}Skipping missing edited photo for {filename}")
results.missing.append(str(pathlib.Path(dest_path) / filename)) results.missing.append(str(pathlib.Path(dest_path) / filename))
continue return results
elif ( elif (
photo.intrash photo.intrash
and (not photo.path_edited or use_photos_export) and (not photo.path_edited or use_photos_export)
@@ -2839,7 +2846,7 @@ def export_photo_with_template(
f"{space}Skipping missing deleted photo {photo.original_filename} ({photo.uuid})" f"{space}Skipping missing deleted photo {photo.original_filename} ({photo.uuid})"
) )
results.missing.append(str(pathlib.Path(dest_path) / filename)) results.missing.append(str(pathlib.Path(dest_path) / filename))
continue return results
render_options = RenderOptions(export_dir=export_dir, dest_path=dest_path) render_options = RenderOptions(export_dir=export_dir, dest_path=dest_path)
@@ -2948,6 +2955,8 @@ def export_photo_with_template(
def get_filenames_from_template( def get_filenames_from_template(
photo, photo,
filename_template, filename_template,
export_dir,
dest_path,
original_name, original_name,
strip=False, strip=False,
edited=False, edited=False,
@@ -2958,6 +2967,7 @@ def get_filenames_from_template(
photo: a PhotoInfo instance photo: a PhotoInfo instance
filename_template: a PhotoTemplate template string, may be None filename_template: a PhotoTemplate template string, may be None
original_name: boolean; if True, use photo's original filename instead of current filename 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 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 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, filename=True,
strip=strip, strip=strip,
edited_version=edited, edited_version=edited,
export_dir=export_dir,
dest_path=dest_path,
) )
filenames, unmatched = photo.render_template(filename_template, options) filenames, unmatched = photo.render_template(filename_template, options)
except ValueError as e: except ValueError as e: