Added {filepath} template field in prep for --post-command and other goodies
This commit is contained in:
25
README.md
25
README.md
@@ -1505,7 +1505,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.31'
|
{osxphotos_version} The osxphotos version, e.g. '0.42.32'
|
||||||
{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
|
||||||
@@ -1574,19 +1574,26 @@ Substitution Description
|
|||||||
/blob/master/examples/template_function.py for an
|
/blob/master/examples/template_function.py for an
|
||||||
example of how to implement a template function.
|
example of how to implement a template function.
|
||||||
|
|
||||||
The following substitutions are 'path-like'. You can access various parts of the
|
The following substitutions are file or directory paths. You can access various
|
||||||
path using the following modifiers:
|
parts of the path using the following modifiers:
|
||||||
|
|
||||||
{field.parent}: the parent directory
|
{path.parent}: the parent directory
|
||||||
{field.name}: the name of the file or final sub-directory
|
{path.name}: the name of the file or final sub-directory
|
||||||
{field.stem}: the name of the file without the extension
|
{path.stem}: the name of the file without the extension
|
||||||
{field.suffix}: the suffix of the file including the leading '.'
|
{path.suffix}: the suffix of the file including the leading '.'
|
||||||
|
|
||||||
For example, if the field {export_dir} is '/Shared/Backup/Photos',
|
For example, if the field {export_dir} is '/Shared/Backup/Photos':
|
||||||
{export_dir.parent} is '/Shared/Backup'
|
{export_dir.parent} is '/Shared/Backup'
|
||||||
|
|
||||||
|
If the field {filepath} is '/Shared/Backup/Photos/IMG_1234.JPG':
|
||||||
|
{filepath.parent} is '/Shared/Backup/Photos'
|
||||||
|
{filepath.name} is 'IMG_1234.JPG'
|
||||||
|
{filepath.stem} is 'IMG_1234'
|
||||||
|
{filepath.suffix} is '.JPG'
|
||||||
|
|
||||||
Substitution Description
|
Substitution Description
|
||||||
{export_dir} The full path to the export directory
|
{export_dir} The full path to the export directory
|
||||||
|
{filepath} The full path to the exported file
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -3216,7 +3223,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.31'|
|
|{osxphotos_version}|The osxphotos version, e.g. '0.42.32'|
|
||||||
|{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|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
""" version info """
|
""" version info """
|
||||||
|
|
||||||
__version__ = "0.42.32"
|
__version__ = "0.42.33"
|
||||||
|
|||||||
@@ -1630,6 +1630,7 @@ def export(
|
|||||||
jpeg_ext=jpeg_ext,
|
jpeg_ext=jpeg_ext,
|
||||||
replace_keywords=replace_keywords,
|
replace_keywords=replace_keywords,
|
||||||
retry=retry,
|
retry=retry,
|
||||||
|
export_dir=dest,
|
||||||
)
|
)
|
||||||
|
|
||||||
if album_export and export_results.exported:
|
if album_export and export_results.exported:
|
||||||
@@ -2251,6 +2252,7 @@ def export_photo(
|
|||||||
jpeg_ext=None,
|
jpeg_ext=None,
|
||||||
replace_keywords=False,
|
replace_keywords=False,
|
||||||
retry=0,
|
retry=0,
|
||||||
|
export_dir=None,
|
||||||
):
|
):
|
||||||
"""Helper function for export that does the actual export
|
"""Helper function for export that does the actual export
|
||||||
|
|
||||||
@@ -2291,6 +2293,7 @@ def export_photo(
|
|||||||
jpeg_ext: if not None, specify the extension to use for all JPEG images on export
|
jpeg_ext: if not None, specify the extension to use for all JPEG images on export
|
||||||
replace_keywords: if True, --keyword-template replaces keywords instead of adding keywords
|
replace_keywords: if True, --keyword-template replaces keywords instead of adding keywords
|
||||||
retry: retry up to retry # of times if there's an error
|
retry: retry up to retry # of times if there's an error
|
||||||
|
export_dir: top-level export directory for {export_dir} template
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list of path(s) of exported photo or None if photo was missing
|
list of path(s) of exported photo or None if photo was missing
|
||||||
@@ -2449,6 +2452,7 @@ def export_photo(
|
|||||||
jpeg_ext=jpeg_ext,
|
jpeg_ext=jpeg_ext,
|
||||||
replace_keywords=replace_keywords,
|
replace_keywords=replace_keywords,
|
||||||
retry=retry,
|
retry=retry,
|
||||||
|
export_dir=export_dir,
|
||||||
)
|
)
|
||||||
|
|
||||||
if export_edited and photo.hasadjustments:
|
if export_edited and photo.hasadjustments:
|
||||||
@@ -2553,6 +2557,7 @@ def export_photo(
|
|||||||
jpeg_ext=jpeg_ext,
|
jpeg_ext=jpeg_ext,
|
||||||
replace_keywords=replace_keywords,
|
replace_keywords=replace_keywords,
|
||||||
retry=retry,
|
retry=retry,
|
||||||
|
export_dir=export_dir,
|
||||||
)
|
)
|
||||||
|
|
||||||
return results
|
return results
|
||||||
@@ -2597,6 +2602,7 @@ def export_photo_with_template(
|
|||||||
jpeg_ext,
|
jpeg_ext,
|
||||||
replace_keywords,
|
replace_keywords,
|
||||||
retry,
|
retry,
|
||||||
|
export_dir,
|
||||||
):
|
):
|
||||||
"""Evaluate directory template then export photo to each directory"""
|
"""Evaluate directory template then export photo to each directory"""
|
||||||
|
|
||||||
@@ -2647,6 +2653,8 @@ def export_photo_with_template(
|
|||||||
results.missing.append(str(pathlib.Path(dest_path) / filename))
|
results.missing.append(str(pathlib.Path(dest_path) / filename))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
render_options = RenderOptions(export_dir=export_dir)
|
||||||
|
|
||||||
tries = 0
|
tries = 0
|
||||||
while tries <= retry:
|
while tries <= retry:
|
||||||
tries += 1
|
tries += 1
|
||||||
@@ -2684,6 +2692,7 @@ def export_photo_with_template(
|
|||||||
exiftool_flags=exiftool_option,
|
exiftool_flags=exiftool_option,
|
||||||
jpeg_ext=jpeg_ext,
|
jpeg_ext=jpeg_ext,
|
||||||
replace_keywords=replace_keywords,
|
replace_keywords=replace_keywords,
|
||||||
|
render_options=render_options,
|
||||||
)
|
)
|
||||||
for warning_ in export_results.exiftool_warning:
|
for warning_ in export_results.exiftool_warning:
|
||||||
verbose_(f"exiftool warning for file {warning_[0]}: {warning_[1]}")
|
verbose_(f"exiftool warning for file {warning_[0]}: {warning_[1]}")
|
||||||
|
|||||||
@@ -163,21 +163,27 @@ The following attributes may be used with '--xattr-template':
|
|||||||
|
|
||||||
formatter.write("\n")
|
formatter.write("\n")
|
||||||
formatter.write_text(
|
formatter.write_text(
|
||||||
"The following substitutions are 'path-like'. "
|
"The following substitutions are file or directory paths. "
|
||||||
+ "You can access various parts of the path using the following modifiers:"
|
+ "You can access various parts of the path using the following modifiers:"
|
||||||
)
|
)
|
||||||
formatter.write("\n")
|
formatter.write("\n")
|
||||||
formatter.write("{field.parent}: the parent directory\n")
|
formatter.write("{path.parent}: the parent directory\n")
|
||||||
formatter.write("{field.name}: the name of the file or final sub-directory\n")
|
formatter.write("{path.name}: the name of the file or final sub-directory\n")
|
||||||
formatter.write("{field.stem}: the name of the file without the extension\n")
|
formatter.write("{path.stem}: the name of the file without the extension\n")
|
||||||
formatter.write(
|
formatter.write(
|
||||||
"{field.suffix}: the suffix of the file including the leading '.'\n"
|
"{path.suffix}: the suffix of the file including the leading '.'\n"
|
||||||
)
|
)
|
||||||
formatter.write("\n")
|
formatter.write("\n")
|
||||||
formatter.write_text(
|
formatter.write(
|
||||||
"For example, if the field {export_dir} is '/Shared/Backup/Photos', "
|
"For example, if the field {export_dir} is '/Shared/Backup/Photos':\n")
|
||||||
+ "{export_dir.parent} is '/Shared/Backup'"
|
formatter.write("{export_dir.parent} is '/Shared/Backup'\n")
|
||||||
)
|
formatter.write("\n")
|
||||||
|
formatter.write(
|
||||||
|
"If the field {filepath} is '/Shared/Backup/Photos/IMG_1234.JPG':\n")
|
||||||
|
formatter.write("{filepath.parent} is '/Shared/Backup/Photos'\n")
|
||||||
|
formatter.write("{filepath.name} is 'IMG_1234.JPG'\n")
|
||||||
|
formatter.write("{filepath.stem} is 'IMG_1234'\n")
|
||||||
|
formatter.write("{filepath.suffix} is '.JPG'\n")
|
||||||
formatter.write("\n")
|
formatter.write("\n")
|
||||||
templ_tuples = [("Substitution", "Description")]
|
templ_tuples = [("Substitution", "Description")]
|
||||||
templ_tuples.extend((k, v) for k, v in TEMPLATE_SUBSTITUTIONS_PATHLIB.items())
|
templ_tuples.extend((k, v) for k, v in TEMPLATE_SUBSTITUTIONS_PATHLIB.items())
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
# TODO: should this be its own PhotoExporter class?
|
# TODO: should this be its own PhotoExporter class?
|
||||||
# TODO: the various sidecar_json, sidecar_xmp, etc args should all be collapsed to a sidecar param using a bit mask
|
# TODO: the various sidecar_json, sidecar_xmp, etc args should all be collapsed to a sidecar param using a bit mask
|
||||||
|
|
||||||
|
import dataclasses
|
||||||
import glob
|
import glob
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
@@ -24,6 +25,7 @@ import pathlib
|
|||||||
import re
|
import re
|
||||||
import tempfile
|
import tempfile
|
||||||
from collections import namedtuple # pylint: disable=syntax-error
|
from collections import namedtuple # pylint: disable=syntax-error
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import photoscript
|
import photoscript
|
||||||
from mako.template import Template
|
from mako.template import Template
|
||||||
@@ -391,7 +393,7 @@ def export(
|
|||||||
use_persons_as_keywords=False,
|
use_persons_as_keywords=False,
|
||||||
keyword_template=None,
|
keyword_template=None,
|
||||||
description_template=None,
|
description_template=None,
|
||||||
export_dir=None,
|
render_options: Optional[RenderOptions] = None,
|
||||||
):
|
):
|
||||||
"""export photo
|
"""export photo
|
||||||
dest: must be valid destination path (or exception raised)
|
dest: must be valid destination path (or exception raised)
|
||||||
@@ -429,7 +431,7 @@ def export(
|
|||||||
when exporting metadata with exiftool or sidecar
|
when exporting metadata with exiftool or sidecar
|
||||||
keyword_template: (list of strings); list of template strings that will be rendered as used as keywords
|
keyword_template: (list of strings); list of template strings that will be rendered as used as keywords
|
||||||
description_template: string; optional template string that will be rendered for use as photo description
|
description_template: string; optional template string that will be rendered for use as photo description
|
||||||
export_dir: value to use for {export_dir} template
|
render_options: an optional osxphotos.phototemplate.RenderOptions instance with options to pass to template renderer
|
||||||
|
|
||||||
Returns: list of photos exported
|
Returns: list of photos exported
|
||||||
"""
|
"""
|
||||||
@@ -461,7 +463,7 @@ def export(
|
|||||||
use_persons_as_keywords=use_persons_as_keywords,
|
use_persons_as_keywords=use_persons_as_keywords,
|
||||||
keyword_template=keyword_template,
|
keyword_template=keyword_template,
|
||||||
description_template=description_template,
|
description_template=description_template,
|
||||||
export_dir=export_dir,
|
render_options = render_options,
|
||||||
)
|
)
|
||||||
|
|
||||||
return results.exported
|
return results.exported
|
||||||
@@ -504,7 +506,7 @@ def export2(
|
|||||||
persons=True,
|
persons=True,
|
||||||
location=True,
|
location=True,
|
||||||
replace_keywords=False,
|
replace_keywords=False,
|
||||||
export_dir=None,
|
render_options: Optional[RenderOptions] = None
|
||||||
):
|
):
|
||||||
"""export photo, like export but with update and dry_run options
|
"""export photo, like export but with update and dry_run options
|
||||||
dest: must be valid destination path or exception raised
|
dest: must be valid destination path or exception raised
|
||||||
@@ -560,7 +562,7 @@ def export2(
|
|||||||
persons: if True, include persons in exported metadata
|
persons: if True, include persons in exported metadata
|
||||||
location: if True, include location in exported metadata
|
location: if True, include location in exported metadata
|
||||||
replace_keywords: if True, keyword_template replaces any keywords, otherwise it's additive
|
replace_keywords: if True, keyword_template replaces any keywords, otherwise it's additive
|
||||||
export_dir: value to use for {export_dir} template
|
render_options: optional osxphotos.phototemplate.RenderOptions instance to specify options for rendering templates
|
||||||
|
|
||||||
Returns: ExportResults class
|
Returns: ExportResults class
|
||||||
ExportResults has attributes:
|
ExportResults has attributes:
|
||||||
@@ -602,6 +604,8 @@ def export2(
|
|||||||
if verbose is None:
|
if verbose is None:
|
||||||
verbose = self._verbose
|
verbose = self._verbose
|
||||||
|
|
||||||
|
self._render_options = render_options or RenderOptions()
|
||||||
|
|
||||||
# suffix to add to edited files
|
# suffix to add to edited files
|
||||||
# e.g. name will be filename_edited.jpg
|
# e.g. name will be filename_edited.jpg
|
||||||
edited_identifier = "_edited"
|
edited_identifier = "_edited"
|
||||||
@@ -685,6 +689,7 @@ def export2(
|
|||||||
f"destination exists ({dest}); overwrite={overwrite}, increment={increment}"
|
f"destination exists ({dest}); overwrite={overwrite}, increment={increment}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._render_options.filepath = str(dest)
|
||||||
all_results = ExportResults()
|
all_results = ExportResults()
|
||||||
if not use_photos_export:
|
if not use_photos_export:
|
||||||
# find the source file on disk and export
|
# find the source file on disk and export
|
||||||
@@ -1598,7 +1603,7 @@ def _exiftool_dict(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if description_template is not None:
|
if description_template is not None:
|
||||||
options = RenderOptions(expand_inplace=True, inplace_sep=", ")
|
options = dataclasses.replace(self._render_options, expand_inplace=True, inplace_sep=", ")
|
||||||
rendered = self.render_template(description_template, options)[0]
|
rendered = self.render_template(description_template, options)[0]
|
||||||
description = " ".join(rendered) if rendered else ""
|
description = " ".join(rendered) if rendered else ""
|
||||||
exif["EXIF:ImageDescription"] = description
|
exif["EXIF:ImageDescription"] = description
|
||||||
@@ -1637,7 +1642,7 @@ def _exiftool_dict(
|
|||||||
|
|
||||||
if keyword_template:
|
if keyword_template:
|
||||||
rendered_keywords = []
|
rendered_keywords = []
|
||||||
options = RenderOptions(none_str=_OSXPHOTOS_NONE_SENTINEL, path_sep="/")
|
options = dataclasses.replace(self._render_options, none_str=_OSXPHOTOS_NONE_SENTINEL, path_sep="/")
|
||||||
for template_str in keyword_template:
|
for template_str in keyword_template:
|
||||||
rendered, unmatched = self.render_template(template_str, options)
|
rendered, unmatched = self.render_template(template_str, options)
|
||||||
if unmatched:
|
if unmatched:
|
||||||
@@ -1915,7 +1920,7 @@ def _xmp_sidecar(
|
|||||||
extension = extension.suffix[1:] if extension.suffix else None
|
extension = extension.suffix[1:] if extension.suffix else None
|
||||||
|
|
||||||
if description_template is not None:
|
if description_template is not None:
|
||||||
options = RenderOptions(expand_inplace=True, inplace_sep=", ")
|
options = dataclasses.replace(self._render_options, expand_inplace=True, inplace_sep=", ")
|
||||||
rendered = self.render_template(description_template, options)[0]
|
rendered = self.render_template(description_template, options)[0]
|
||||||
description = " ".join(rendered) if rendered else ""
|
description = " ".join(rendered) if rendered else ""
|
||||||
else:
|
else:
|
||||||
@@ -1948,7 +1953,7 @@ def _xmp_sidecar(
|
|||||||
|
|
||||||
if keyword_template:
|
if keyword_template:
|
||||||
rendered_keywords = []
|
rendered_keywords = []
|
||||||
options = RenderOptions(none_str=_OSXPHOTOS_NONE_SENTINEL, path_sep="/")
|
options = dataclasses.replace(self._render_options, none_str=_OSXPHOTOS_NONE_SENTINEL, path_sep="/")
|
||||||
for template_str in keyword_template:
|
for template_str in keyword_template:
|
||||||
rendered, unmatched = self.render_template(template_str, options)
|
rendered, unmatched = self.render_template(template_str, options)
|
||||||
if unmatched:
|
if unmatched:
|
||||||
|
|||||||
@@ -78,6 +78,9 @@ class PhotoInfo:
|
|||||||
self._db = db
|
self._db = db
|
||||||
self._verbose = self._db._verbose
|
self._verbose = self._db._verbose
|
||||||
|
|
||||||
|
# TODO: remove this once refactor of PhotoExporter is done
|
||||||
|
self._render_options = RenderOptions()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filename(self):
|
def filename(self):
|
||||||
"""filename of the picture"""
|
"""filename of the picture"""
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ TEMPLATE_SUBSTITUTIONS = {
|
|||||||
|
|
||||||
TEMPLATE_SUBSTITUTIONS_PATHLIB = {
|
TEMPLATE_SUBSTITUTIONS_PATHLIB = {
|
||||||
"{export_dir}": "The full path to the export directory",
|
"{export_dir}": "The full path to the export directory",
|
||||||
|
"{filepath}": "The full path to the exported file",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Permitted multi-value substitutions (each of these returns None or 1 or more values)
|
# Permitted multi-value substitutions (each of these returns None or 1 or more values)
|
||||||
@@ -504,9 +505,7 @@ class PhotoTemplate:
|
|||||||
path_sep=path_sep,
|
path_sep=path_sep,
|
||||||
)
|
)
|
||||||
elif field.split(".")[0] in PATHLIB_SUBSTITUTIONS:
|
elif field.split(".")[0] in PATHLIB_SUBSTITUTIONS:
|
||||||
vals = self.get_template_value_pathlib(
|
vals = self.get_template_value_pathlib(field)
|
||||||
field,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
unmatched.append(field)
|
unmatched.append(field)
|
||||||
return [], unmatched
|
return [], unmatched
|
||||||
@@ -931,10 +930,7 @@ class PhotoTemplate:
|
|||||||
|
|
||||||
return [value]
|
return [value]
|
||||||
|
|
||||||
def get_template_value_pathlib(
|
def get_template_value_pathlib(self, field):
|
||||||
self,
|
|
||||||
field,
|
|
||||||
):
|
|
||||||
"""lookup value for template pathlib template fields
|
"""lookup value for template pathlib template fields
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -946,10 +942,17 @@ class PhotoTemplate:
|
|||||||
Raises:
|
Raises:
|
||||||
ValueError if no rule exists for field.
|
ValueError if no rule exists for field.
|
||||||
"""
|
"""
|
||||||
if field.split(".")[0] not in PATHLIB_SUBSTITUTIONS:
|
field_stem = field.split(".")[0]
|
||||||
|
if field_stem not in PATHLIB_SUBSTITUTIONS:
|
||||||
raise ValueError(f"SyntaxError: Unknown field: {field}")
|
raise ValueError(f"SyntaxError: Unknown field: {field}")
|
||||||
|
|
||||||
value = _get_pathlib_value(field, self.export_dir)
|
field_value = None
|
||||||
|
try:
|
||||||
|
field_value = getattr(self, field_stem)
|
||||||
|
except AttributeError:
|
||||||
|
raise ValueError(f"Unknown path-like field: {field_stem}")
|
||||||
|
|
||||||
|
value = _get_pathlib_value(field, field_value)
|
||||||
|
|
||||||
if self.filename:
|
if self.filename:
|
||||||
value = sanitize_pathpart(value)
|
value = sanitize_pathpart(value)
|
||||||
|
|||||||
@@ -473,6 +473,7 @@ CLI_UUID_DICT_15_7 = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CLI_TEMPLATE_SIDECAR_FILENAME = "Pumkins1.jpg.json"
|
CLI_TEMPLATE_SIDECAR_FILENAME = "Pumkins1.jpg.json"
|
||||||
|
CLI_TEMPLATE_FILENAME = "Pumkins1.jpg"
|
||||||
|
|
||||||
CLI_UUID_DICT_14_6 = {"intrash": "3tljdX43R8+k6peNHVrJNQ"}
|
CLI_UUID_DICT_14_6 = {"intrash": "3tljdX43R8+k6peNHVrJNQ"}
|
||||||
|
|
||||||
@@ -6286,3 +6287,79 @@ def test_query_regex_4():
|
|||||||
json_got = json.loads(result.output)
|
json_got = json.loads(result.output)
|
||||||
|
|
||||||
assert len(json_got) == 2
|
assert len(json_got) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_export_export_dir_template():
|
||||||
|
"""Test {export_dir} template"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import osxphotos
|
||||||
|
|
||||||
|
from osxphotos.cli import cli
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
cwd = os.getcwd()
|
||||||
|
# pylint: disable=not-context-manager
|
||||||
|
with runner.isolated_filesystem():
|
||||||
|
isolated_cwd = os.getcwd()
|
||||||
|
result = runner.invoke(
|
||||||
|
cli,
|
||||||
|
[
|
||||||
|
"export",
|
||||||
|
"--db",
|
||||||
|
os.path.join(cwd, PHOTOS_DB_15_7),
|
||||||
|
".",
|
||||||
|
"--sidecar=json",
|
||||||
|
f"--uuid={CLI_UUID_DICT_15_7['template']}",
|
||||||
|
"-V",
|
||||||
|
"--keyword-template",
|
||||||
|
"{person}",
|
||||||
|
"--description-template",
|
||||||
|
"{export_dir}",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert os.path.isfile(CLI_TEMPLATE_SIDECAR_FILENAME)
|
||||||
|
with open(CLI_TEMPLATE_SIDECAR_FILENAME, "r") as jsonfile:
|
||||||
|
exifdata = json.load(jsonfile)
|
||||||
|
assert exifdata[0]["XMP:Description"] == isolated_cwd
|
||||||
|
|
||||||
|
|
||||||
|
def test_export_filepath_template():
|
||||||
|
"""Test {filepath} template"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import osxphotos
|
||||||
|
|
||||||
|
from osxphotos.cli import cli
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
cwd = os.getcwd()
|
||||||
|
# pylint: disable=not-context-manager
|
||||||
|
with runner.isolated_filesystem():
|
||||||
|
isolated_cwd = os.getcwd()
|
||||||
|
result = runner.invoke(
|
||||||
|
cli,
|
||||||
|
[
|
||||||
|
"export",
|
||||||
|
"--db",
|
||||||
|
os.path.join(cwd, PHOTOS_DB_15_7),
|
||||||
|
".",
|
||||||
|
"--sidecar=json",
|
||||||
|
f"--uuid={CLI_UUID_DICT_15_7['template']}",
|
||||||
|
"-V",
|
||||||
|
"--keyword-template",
|
||||||
|
"{person}",
|
||||||
|
"--description-template",
|
||||||
|
"{filepath}",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert os.path.isfile(CLI_TEMPLATE_SIDECAR_FILENAME)
|
||||||
|
with open(CLI_TEMPLATE_SIDECAR_FILENAME, "r") as jsonfile:
|
||||||
|
exifdata = json.load(jsonfile)
|
||||||
|
assert exifdata[0]["XMP:Description"] == os.path.join(
|
||||||
|
isolated_cwd, CLI_TEMPLATE_FILENAME
|
||||||
|
)
|
||||||
|
|||||||
@@ -1041,3 +1041,29 @@ def test_export_dir():
|
|||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
rendered, _ = template.render("{export_dir.foo}", options)
|
rendered, _ = template.render("{export_dir.foo}", options)
|
||||||
|
|
||||||
|
|
||||||
|
def test_filepath():
|
||||||
|
"""Test {filepath} template"""
|
||||||
|
from osxphotos.photoinfo import PhotoInfoNone
|
||||||
|
from osxphotos.phototemplate import PhotoTemplate
|
||||||
|
|
||||||
|
options = RenderOptions(filepath="/foo/bar.jpeg")
|
||||||
|
template = PhotoTemplate(PhotoInfoNone())
|
||||||
|
rendered, _ = template.render("{filepath}", options)
|
||||||
|
assert rendered[0] == "/foo/bar.jpeg"
|
||||||
|
|
||||||
|
rendered, _ = template.render("{filepath.name}", options)
|
||||||
|
assert rendered[0] == "bar.jpeg"
|
||||||
|
|
||||||
|
rendered, _ = template.render("{filepath.parent}", options)
|
||||||
|
assert rendered[0] == "/foo"
|
||||||
|
|
||||||
|
rendered, _ = template.render("{filepath.stem}", options)
|
||||||
|
assert rendered[0] == "bar"
|
||||||
|
|
||||||
|
rendered, _ = template.render("{filepath.suffix}", options)
|
||||||
|
assert rendered[0] == ".jpeg"
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
rendered, _ = template.render("{filepath.foo}", options)
|
||||||
|
|||||||
Reference in New Issue
Block a user