13 KiB
The templating system converts one or template statements, written in osxphotos metadata templating language, to one or more rendered values using information from the photo being processed.
In its simplest form, a template statement has the form: "{template_field}", for example "{title}" which would resolve to the title of the photo.
Template statements may contain one or more modifiers. The full syntax is:
"pretext{delim+template_field:subfield(field_arg)|filter[find,replace] conditional?bool_value,default}posttext"
Template statements are white-space sensitive meaning that white space (spaces, tabs) changes the meaning of the template statement.
pretext and posttext are free form text. For example, if a photo has title "My Photo Title" the template statement "The title of the photo is {title}", resolves to "The title of the photo is My Photo Title". The pretext in this example is "The title if the photo is " and the template_field is {title}.
delim: optional delimiter string to use when expanding multi-valued template values in-place
+: If present before template name, expands the template in place. If delim not provided, values are joined with no delimiter.
e.g. if Photo keywords are ["foo","bar"]:
"{keyword}"renders to"foo", "bar""{,+keyword}"renders to:"foo,bar""{; +keyword}"renders to:"foo; bar""{+keyword}"renders to"foobar"
template_field: The template field to resolve. See Template Substitutions for full list of template fields.
:subfield: Some templates have sub-fields, For example, {exiftool:IPTC:Make}; the template_field is exiftool and the sub-field is IPTC:Make.
(field_arg): optional arguments to pass to the field; for example, with {folder_album} this is used to pass the path separator used for joining folders and albums when rendering the field (default is "/" for {folder_album}).
|filter: You may optionally append one or more filter commands to the end of the template field using the vertical pipe ('|') symbol. Filters may be combined, separated by '|' as in: {keyword|capitalize|parens}.
Valid filters are:
lower: Convert value to lower case, e.g. 'Value' => 'value'.upper: Convert value to upper case, e.g. 'Value' => 'VALUE'.strip: Strip whitespace from beginning/end of value, e.g. ' Value ' => 'Value'.titlecase: Convert value to title case, e.g. 'my value' => 'My Value'.capitalize: Capitalize first word of value and convert other words to lower case, e.g. 'MY VALUE' => 'My value'.braces: Enclose value in curly braces, e.g. 'value => '{value}'.parens: Enclose value in parentheses, e.g. 'value' => '(value')brackets: Enclose value in brackets, e.g. 'value' => '[value]'shell_quote: Quotes the value for safe usage in the shell, e.g. My file.jpeg => 'My file.jpeg'; only adds quotes if needed.function: Run custom python function to filter value; use in format 'function:/path/to/file.py::function_name'. See example at https://github.com/RhetTbull/osxphotos/blob/master/examples/template_filter.pysplit(x): Split value into a list of values using x as delimiter, e.g. 'value1;value2' => ['value1', 'value2'] if used with split(;).autosplit: Automatically split delimited string into separate values; will split strings delimited by comma, semicolon, or space, e.g. 'value1,value2' => ['value1', 'value2'].chop(x): Remove x characters off the end of value, e.g. chop(1): 'Value' => 'Valu'; when applied to a list, chops characters from each list value, e.g. chop(1): ['travel', 'beach']=> ['trave', 'beac'].chomp(x): Remove x characters from the beginning of value, e.g. chomp(1): ['Value'] => ['alue']; when applied to a list, removes characters from each list value, e.g. chomp(1): ['travel', 'beach']=> ['ravel', 'each'].sort: Sort list of values, e.g. ['c', 'b', 'a'] => ['a', 'b', 'c'].rsort: Sort list of values in reverse order, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].reverse: Reverse order of values, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].uniq: Remove duplicate values, e.g. ['a', 'b', 'c', 'b', 'a'] => ['a', 'b', 'c'].join(x): Join list of values with delimiter x, e.g. join(,): ['a', 'b', 'c'] => 'a,b,c'; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters.May optionally be used without an argument, that is 'join()' which joins values together with no delimiter. e.g. join(): ['a', 'b', 'c'] => 'abc'.append(x): Append x to list of values, e.g. append(d): ['a', 'b', 'c'] => ['a', 'b', 'c', 'd'].prepend(x): Prepend x to list of values, e.g. prepend(d): ['a', 'b', 'c'] => ['d', 'a', 'b', 'c'].remove(x): Remove x from list of values, e.g. remove(b): ['a', 'b', 'c'] => ['a', 'c'].slice(start:stop:step): Slice list using same semantics as Python's list slicing, e.g. slice(1:3): ['a', 'b', 'c', 'd'] => ['b', 'c']; slice(1:4:2): ['a', 'b', 'c', 'd'] => ['b', 'd']; slice(1:): ['a', 'b', 'c', 'd'] => ['b', 'c', 'd']; slice(:-1): ['a', 'b', 'c', 'd'] => ['a', 'b', 'c']; slice(::-1): ['a', 'b', 'c', 'd'] => ['d', 'c', 'b', 'a']. See also sslice().sslice(start:stop:step): [s(tring) slice] Slice values in a list using same semantics as Python's string slicing, e.g. sslice(1:3):'abcd => 'bc'; sslice(1:4:2): 'abcd' => 'bd', etc. See also slice().filter(x): Filter list of values using predicate x; for example,{folder_album|filter(contains Events)}returns only folders/albums containing the word 'Events' in their path.int: Convert values in list to integer, e.g. 1.0 => 1. If value cannot be converted to integer, remove value from list. ['1.1', 'x'] => ['1']. See also float.float: Convert values in list to floating point number, e.g. 1 => 1.0. If value cannot be converted to float, remove value from list. ['1', 'x'] => ['1.0']. See also int.
e.g. if Photo keywords are ["FOO","bar"]:
"{keyword|lower}"renders to"foo", "bar""{keyword|upper}"renders to:"FOO", "BAR""{keyword|capitalize}"renders to:"Foo", "Bar""{keyword|lower|parens}"renders to:"(foo)", "(bar)"
e.g. if Photo description is "my description":
"{descr|titlecase}"renders to:"My Description"
e.g. If Photo is in Album1 in Folder1:
"{folder_album}"renders to["Folder1/Album1"]"{folder_album(>)}"renders to["Folder1>Album1"]"{folder_album()}"renders to["Folder1Album1"]
[find,replace]: optional text replacement to perform on rendered template value. For example, to replace "/" in an album name, you could use the template "{album[/,-]}". Multiple replacements can be made by appending "|" and adding another find|replace pair. e.g. to replace both "/" and ":" in album name: "{album[/,-|:,-]}". find/replace pairs are not limited to single characters. The "|" character cannot be used in a find/replace pair.
conditional: optional conditional expression that is evaluated as boolean (True/False) for use with the ?bool_value modifier. Conditional expressions take the form 'not operator value' where not is an optional modifier that negates the operator. Note: the space before the conditional expression is required if you use a conditional expression. Valid comparison operators are:
contains: template field contains value, similar to python'sinmatches: template field contains exactly value, unlikecontains: does not match partial matchesstartswith: template field starts with valueendswith: template field ends with value<=: template field is less than or equal to value>=: template field is greater than or equal to value<: template field is less than value>: template field is greater than value==: template field equals value!=: template field does not equal value
The value part of the conditional expression is treated as a bare (unquoted) word/phrase. Multiple values may be separated by '|' (the pipe symbol). value is itself a template statement so you can use one or more template fields in value which will be resolved before the comparison occurs.
For example:
{keyword matches Beach}resolves to True if 'Beach' is a keyword. It would not match keyword 'BeachDay'.{keyword contains Beach}resolves to True if any keyword contains the word 'Beach' so it would match both 'Beach' and 'BeachDay'.{photo.score.overall > 0.7}resolves to True if the photo's overall aesthetic score is greater than 0.7.{keyword|lower contains beach}uses the lower case filter to do case-insensitive matching to match any keyword that contains the word 'beach'.{keyword|lower not contains beach}uses thenotmodifier to negate the comparison so this resolves to True if there is no keyword that matches 'beach'.
Examples: to export photos that contain certain keywords with the osxphotos export command's --directory option:
--directory "{keyword|lower matches travel|vacation?Travel-Photos,Not-Travel-Photos}"
This exports any photo that has keywords 'travel' or 'vacation' into a directory 'Travel-Photos' and all other photos into directory 'Not-Travel-Photos'.
This can be used to rename files as well, for example:
--filename "{favorite?Favorite-{original_name},{original_name}}"
This renames any photo that is a favorite as 'Favorite-ImageName.jpg' (where 'ImageName.jpg' is the original name of the photo) and all other photos with the unmodified original name.
?bool_value: Template fields may be evaluated as boolean (True/False) by appending "?" after the field name (and following "(field_arg)" or "[find/replace]". If a field is True (e.g. photo is HDR and field is "{hdr}") or has any value, the value following the "?" will be used to render the template instead of the actual field value. If the template field evaluates to False (e.g. in above example, photo is not HDR) or has no value (e.g. photo has no title and field is "{title}") then the default value following a "," will be used.
e.g. if photo is an HDR image,
"{hdr?ISHDR,NOTHDR}"renders to"ISHDR"
and if it is not an HDR image,
"{hdr?ISHDR,NOTHDR}"renders to"NOTHDR"
,default: optional default value to use if the template name has no value. This modifier is also used for the value if False for boolean-type fields (see above) as well as to hold a sub-template for values like {created.strftime}. If no default value provided, "_" is used.
e.g., if photo has no title set,
"{title}"renders to "_""{title,I have no title}"renders to"I have no title"
Template fields such as created.strftime use the default value to pass the template to use for strftime.
e.g., if photo date is 4 February 2020, 19:07:38,
"{created.strftime,%Y-%m-%d-%H%M%S}"renders to"2020-02-04-190738"
Some template fields such as "{media_type}" use the default value to allow customization of the output. For example, "{media_type}" resolves to the special media type of the photo such as panorama or selfie. You may use the default value to override these in form: "{media_type,video=vidéo;time_lapse=vidéo_accélérée}". In this example, if photo was a time_lapse photo, media_type would resolve to vidéo_accélérée instead of time_lapse.
Either or both bool_value or default (False value) may be empty which would result in empty string "" when rendered.
If you want to include "{" or "}" in the output, use "{openbrace}" or "{closebrace}" template substitution.
e.g. "{created.year}/{openbrace}{title}{closebrace}" would result in "2020/{Photo Title}".
Variables
You can define variables for later use in the template string using the format {var:NAME,VALUE}. Variables may then be referenced using the format %NAME. For example: {var:foo,bar} defines the variable %foo to have value bar. This can be useful if you want to re-use a complex template value in multiple places within your template string or for allowing the use of characters that would otherwise be prohibited in a template string. For example, the "pipe" (|) character is not allowed in a find/replace pair but you can get around this limitation like so: {var:pipe,{pipe}}{title[-,%pipe]} which replaces the - character with | (the value of %pipe).
Variables can also be referenced as fields in the template string, for example: {var:year,created.year}{original_name}-{%year}. In some cases, use of variables can make your template string more readable. Variables can be used as template fields, as values for filters, as values for conditional operations, or as default values. When used as a conditional value or default value, variables should be treated like any other field and enclosed in braces as conditional and default values are evaluated as template strings. For example: {var:name,Katie}{person contains {%name}?{%name},Not-{%name}}.
If you need to use a % (percent sign character), you can escape the percent sign by using %%. You can also use the {percent} template field where a template field is required. For example:
{title[:,%%]} replaces the : with % and {title contains Foo?{title}{percent},{title}} adds % to the title if it contains Foo.