Pass dest_path to template function via RenderOptions, enable implementation of #496
This commit is contained in:
@@ -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|
|
||||||
|
|||||||
2
build.sh
2
build.sh
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
""" version info """
|
""" version info """
|
||||||
|
|
||||||
__version__ = "0.42.63"
|
__version__ = "0.42.64"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user