diff --git a/README.md b/README.md index d290fe46..0af2b784 100644 --- a/README.md +++ b/README.md @@ -997,9 +997,14 @@ Render template string for photo. none_str is used if template substitution res - `photo`: a [PhotoInfo](#photoinfo) object - `none_str`: optional str to use as substitution when template value is None and no default specified in the template string. default is "_". -Returns a tuple of (rendered, unmatched) where rendered is the rendered template string with all substitutions made and unmatched is a list of any strings that resembled a template substitution but did not match a known substitution. E.g. strings in the form "{foo}". +Returns a tuple of (rendered, unmatched) where rendered is a list of rendered strings with all substitutions made and unmatched is a list of any strings that resembled a template substitution but did not match a known substitution. E.g. if template contained "{foo}", unmatched would be ["foo"]. + +e.g. `render_filepath_template("{created.year}/{foo}", photo)` would return `("2020/{foo}",["foo"])` + +If you want to include "{" or "}" in the output, use "{{" or "}}" + +e.g. `render_filepath_template("{created.year}/{{foo}}", photo)` would return `("2020/{foo}",[])` -e.g. `render_filepath_template("{created.year}/{foo}", photo)` would return `("2020/{foo}",["{foo}"])` | Substitution | Description | |--------------|-------------| diff --git a/osxphotos/__main__.py b/osxphotos/__main__.py index 7909ab28..aecbe786 100644 --- a/osxphotos/__main__.py +++ b/osxphotos/__main__.py @@ -1518,6 +1518,7 @@ def export_photo( dest = create_path_by_date(dest, date_created) elif directory: dirname, unmatched = render_filepath_template(directory, photo) + dirname = dirname[0] if unmatched: click.echo( f"Possible unmatched substitution in template: {unmatched}", err=True diff --git a/osxphotos/_version.py b/osxphotos/_version.py index f5d6d3cd..2064eda9 100644 --- a/osxphotos/_version.py +++ b/osxphotos/_version.py @@ -1,3 +1,3 @@ """ version info """ -__version__ = "0.24.4" +__version__ = "0.24.5" diff --git a/osxphotos/template.py b/osxphotos/template.py index 186ae944..fdbf417e 100644 --- a/osxphotos/template.py +++ b/osxphotos/template.py @@ -1,10 +1,23 @@ +""" Custom template system for osxphotos """ + +# Rolled my own template system because: +# 1. Needed to handle multiple values (e.g. album, keyword) +# 2. Needed to handle default values if template not found +# 3. Didn't want user to need to know python (e.g. by using Mako which is +# already used elsewhere in this project) +# 4. Couldn't figure out how to do #1 and #2 with str.format() +# +# This code isn't elegant but it seems to work well. PRs gladly accepted. + import datetime import pathlib import re -from typing import Tuple # pylint: disable=syntax-error +from typing import Tuple, List # pylint: disable=syntax-error from .photoinfo import PhotoInfo +from ._constants import _UNKNOWN_PERSON +# Permitted substitutions (each of these returns a single value or None) TEMPLATE_SUBSTITUTIONS = { "{name}": "Filename of the photo", "{original_name}": "Photo's original filename when imported to Photos", @@ -39,9 +52,23 @@ TEMPLATE_SUBSTITUTIONS = { "{place.address.country_code}": "ISO country code of the postal address, e.g. 'US'", } +# Permitted multi-value substitutions (each of these returns None or 1 or more values) +TEMPLATE_SUBSTITUTIONS_MULTI_VALUED = { + "{album}": "Album photo is contained in", + "{keyword}": "Keywords assigned to photo", + "{person}": "Person / face in a photo", +} + +# Just the multi-valued substitution names without the braces +MULTI_VALUE_SUBSTITUTIONS = [ + field.replace("{", "").replace("}", "") + for field in TEMPLATE_SUBSTITUTIONS_MULTI_VALUED.keys() +] + def get_template_value(lookup, photo): - """ lookup: value to find a match for + """ lookup template value (single-value template substitutions) for use in make_subst_function + lookup: value to find a match for photo: PhotoInfo object whose data will be used for value substitutions returns: either the matching template value (which may be None) raises: KeyError if no rule exists for lookup """ @@ -202,29 +229,24 @@ def get_template_value(lookup, photo): raise KeyError(f"No rule for processing {lookup}") -def render_filepath_template( - template: str, photo: PhotoInfo, none_str: str = "_" -) -> Tuple[str, list]: - """ render a filename or directory template """ +def render_filepath_template(template, photo, none_str="_"): + """ render a filename or directory template + template: str template + photo: PhotoInfo object + none_str: str to use default for None values, default is '_' """ + # the rendering happens in two phases: + # phase 1: handle all the single-value template substitutions + # results in a single string with all the template fields replaced + # phase 2: loop through all the multi-value template substitutions + # could result in multiple strings + # e.g. if template is "{album}/{person}" and there are 2 albums and 3 persons in the photo + # there would be 6 possible renderings (2 albums x 3 persons) + + # regex to find {template_field,optional_default} in strings + # for explanation of regex see https://regex101.com/r/4JJg42/1 # pylint: disable=anomalous-backslash-in-string - regex = r"""(?