Compare commits

...

4 Commits

Author SHA1 Message Date
Rhet Turnbull
2f57abd23c Fixed modified template to use creation time if no modificationd date, issue #312 2020-12-31 13:07:00 -08:00
Rhet Turnbull
f9a43b92c1 Updated CHANGELOG.md 2020-12-31 12:36:16 -08:00
Rhet Turnbull
bf2a55d7f6 Added --xattr-template, closes #242 2020-12-31 12:33:15 -08:00
Rhet Turnbull
34bb7f2cdc Updated CHANGELOG.md 2020-12-30 20:48:38 -08:00
10 changed files with 382 additions and 98 deletions

View File

@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [v0.39.2](https://github.com/RhetTbull/osxphotos/compare/v0.39.1...v0.39.2)
> 31 December 2020
- Added --xattr-template, closes #242 [`#242`](https://github.com/RhetTbull/osxphotos/issues/242)
#### [v0.39.1](https://github.com/RhetTbull/osxphotos/compare/v0.39.0...v0.39.1)
> 31 December 2020
- Fixed --exiftool-path bug, issue #311, #313 [`3394c52`](https://github.com/RhetTbull/osxphotos/commit/3394c527682d8fdd2f20f4f778d802dab86b6372)
#### [v0.39.0](https://github.com/RhetTbull/osxphotos/compare/v0.38.22...v0.39.0)
> 30 December 2020

145
README.md
View File

@@ -412,20 +412,30 @@ Options:
could specify --description-template
"{descr} exported with osxphotos on
{today.date}" See Templating System below.
--finder-tag-template TEMPLATE Set Finder tags to TEMPLATE. These tags can
be searched in the Finder or Spotlight with
'tag:tagname' format. For example, '--
finder-tag-template "{label}"' to set Finder
tags to photo labels. You may specify
multiple TEMPLATE values by using '--finder-
tag-template' multiple times. See also '--
finder-tag-keywords and Extended Attributes
below.'.
--finder-tag-keywords Set Finder tags to keywords; any keywords
specified via '--keyword-template', '--
person-keyword', etc. will also be used as
Finder tags. See also '--finder-tag-template
and Extended Attributes below.'.
--finder-tag-template TEMPLATE Set MacOS Finder tags to TEMPLATE. These
tags can be searched in the Finder or
Spotlight with 'tag:tagname' format. For
example, '--finder-tag-template "{label}"'
to set Finder tags to photo labels. You may
specify multiple TEMPLATE values by using '
--finder-tag-template' multiple times. See
also '--finder-tag-keywords and Extended
Attributes below.'.
--finder-tag-keywords Set MacOS Finder tags to keywords; any
keywords specified via '--keyword-template',
'--person-keyword', etc. will also be used
as Finder tags. See also '--finder-tag-
template and Extended Attributes below.'.
--xattr-template ATTRIBUTE TEMPLATE
Set extended attribute ATTRIBUTE to TEMPLATE
value. Valid attributes are: 'authors',
'comment', 'copyright', 'description',
'findercomment', 'headline', 'keywords'. For
example, to set Finder comment to the
photo's title and description: '--xattr-
template findercomment "{title}; {descr}"
See Extended Attributes below for additional
details on this option.
--directory DIRECTORY Optional template for specifying name of
output directory in the form
'{name,DEFAULT}'. See below for additional
@@ -530,21 +540,50 @@ option to re-export the entire library thus rebuilding the
** Extended Attributes **
Some options (currently '--finder-tag-template' and '--finder-tag-keywords')
write additional metadata to extended attributes in the file. These options
will only work if the destination filesystem supports extended attributes
(most do). For example, --finder-tag-keyword writes all keywords (including
any specified by '--keyword-template' or other options) to Finder tags that
are searchable in Spotlight using the syntax: 'tag:tagname'. For example, if
you have images with keyword "Travel" then using '--finder-tag-keywords' you
could quickly find those images in the Finder by typing 'tag:Travel' in the
Spotlight search bar. Finder tags are written to the
Some options (currently '--finder-tag-template', '--finder-tag-keywords',
'-xattr-template') write additional metadata to extended attributes in the
file. These options will only work if the destination filesystem supports
extended attributes (most do). For example, --finder-tag-keyword writes all
keywords (including any specified by '--keyword-template' or other options) to
Finder tags that are searchable in Spotlight using the syntax: 'tag:tagname'.
For example, if you have images with keyword "Travel" then using '--finder-
tag-keywords' you could quickly find those images in the Finder by typing
'tag:Travel' in the Spotlight search bar. Finder tags are written to the
'com.apple.metadata:_kMDItemUserTags' extended attribute. Unlike EXIF
metadata, extended attributes do not modify the actual file. Most cloud
storage services do not synch extended attributes. Dropbox does sync them and
any changes to a file's extended attributes will cause Dropbox to re-sync the
files.
The following attributes may be used with '--xattr-template':
authors The author, or authors, of the contents of the file. A list
of strings. (com.apple.metadata:kMDItemAuthors)
comment A comment related to the file. This differs from the Finder
comment, kMDItemFinderComment. A string.
(com.apple.metadata:kMDItemComment)
copyright The copyright owner of the file contents. A string.
(com.apple.metadata:kMDItemCopyright)
description A description of the content of the resource. The
description may include an abstract, table of contents,
reference to a graphical representation of content or a free-
text account of the content. A string.
(com.apple.metadata:kMDItemDescription)
findercomment Finder comments for this file. A string.
(com.apple.metadata:kMDItemFinderComment)
headline A publishable entry providing a synopsis of the contents of
the file. A string. (com.apple.metadata:kMDItemHeadline)
keywords Keywords associated with this file. For example, “Birthday”,
“Important”, etc. This differs from Finder tags
(_kMDItemUserTags) which are keywords/tags shown in the
Finder and searchable in Spotlight using "tag:tag_name". A
list of strings. (com.apple.metadata:kMDItemKeywords)
For additional information on extended attributes see: https://developer.apple
.com/documentation/coreservices/file_metadata/mditem/common_metadata_attribute
_keys
** Templating System **
@@ -701,27 +740,39 @@ Substitution Description
https://strftime.org/ for help on strftime
templates.
{modified.date} Photo's modification date in ISO format,
e.g. '2020-03-22'
{modified.year} 4-digit year of photo modification time
{modified.yy} 2-digit year of photo modification time
e.g. '2020-03-22'; uses creation date if
photo is not modified
{modified.year} 4-digit year of photo modification time;
uses creation date if photo is not modified
{modified.yy} 2-digit year of photo modification time;
uses creation date if photo is not modified
{modified.mm} 2-digit month of the photo modification time
(zero padded)
(zero padded); uses creation date if photo
is not modified
{modified.month} Month name in user's locale of the photo
modification time
modification time; uses creation date if
photo is not modified
{modified.mon} Month abbreviation in the user's locale of
the photo modification time
the photo modification time; uses creation
date if photo is not modified
{modified.dd} 2-digit day of the month (zero padded) of
the photo modification time
the photo modification time; uses creation
date if photo is not modified
{modified.dow} Day of week in user's locale of the photo
modification time
modification time; uses creation date if
photo is not modified
{modified.doy} 3-digit day of year (e.g Julian day) of
photo modification time, starting from 1
(zero padded)
{modified.hour} 2-digit hour of the photo modification time
(zero padded); uses creation date if photo
is not modified
{modified.hour} 2-digit hour of the photo modification time;
uses creation date if photo is not modified
{modified.min} 2-digit minute of the photo modification
time
time; uses creation date if photo is not
modified
{modified.sec} 2-digit second of the photo modification
time
time; uses creation date if photo is not
modified
{today.date} Current date in iso format, e.g.
'2020-03-22'
{today.year} 4-digit year of current date
@@ -2258,18 +2309,18 @@ The following template field substitutions are availabe for use with `PhotoInfo.
|{created.min}|2-digit minute of the photo creation time|
|{created.sec}|2-digit second of the photo creation time|
|{created.strftime}|Apply strftime template to file creation date/time. Should be used in form {created.strftime,TEMPLATE} where TEMPLATE is a valid strftime template, e.g. {created.strftime,%Y-%U} would result in year-week number of year: '2020-23'. If used with no template will return null value. See https://strftime.org/ for help on strftime templates.|
|{modified.date}|Photo's modification date in ISO format, e.g. '2020-03-22'|
|{modified.year}|4-digit year of photo modification time|
|{modified.yy}|2-digit year of photo modification time|
|{modified.mm}|2-digit month of the photo modification time (zero padded)|
|{modified.month}|Month name in user's locale of the photo modification time|
|{modified.mon}|Month abbreviation in the user's locale of the photo modification time|
|{modified.dd}|2-digit day of the month (zero padded) of the photo modification time|
|{modified.dow}|Day of week in user's locale of the photo modification time|
|{modified.doy}|3-digit day of year (e.g Julian day) of photo modification time, starting from 1 (zero padded)|
|{modified.hour}|2-digit hour of the photo modification time|
|{modified.min}|2-digit minute of the photo modification time|
|{modified.sec}|2-digit second of the photo modification time|
|{modified.date}|Photo's modification date in ISO format, e.g. '2020-03-22'; uses creation date if photo is not modified|
|{modified.year}|4-digit year of photo modification time; uses creation date if photo is not modified|
|{modified.yy}|2-digit year of photo modification time; uses creation date if photo is not modified|
|{modified.mm}|2-digit month of the photo modification time (zero padded); uses creation date if photo is not modified|
|{modified.month}|Month name in user's locale of the photo modification time; uses creation date if photo is not modified|
|{modified.mon}|Month abbreviation in the user's locale of the photo modification time; uses creation date if photo is not modified|
|{modified.dd}|2-digit day of the month (zero padded) of the photo modification time; uses creation date if photo is not modified|
|{modified.dow}|Day of week in user's locale of the photo modification time; uses creation date if photo is not modified|
|{modified.doy}|3-digit day of year (e.g Julian day) of photo modification time, starting from 1 (zero padded); uses creation date if photo is not modified|
|{modified.hour}|2-digit hour of the photo modification time; uses creation date if photo is not modified|
|{modified.min}|2-digit minute of the photo modification time; uses creation date if photo is not modified|
|{modified.sec}|2-digit second of the photo modification time; uses creation date if photo is not modified|
|{today.date}|Current date in iso format, e.g. '2020-03-22'|
|{today.year}|4-digit year of current date|
|{today.yy}|2-digit year of current date|

View File

@@ -11,21 +11,23 @@ import time
import unicodedata
import click
import osxmetadata
import yaml
import osxmetadata
import osxphotos
from ._constants import (
_EXIF_TOOL_URL,
_OSXPHOTOS_NONE_SENTINEL,
_PHOTOS_4_VERSION,
_UNKNOWN_PLACE,
_OSXPHOTOS_NONE_SENTINEL,
CLI_COLOR_ERROR,
CLI_COLOR_WARNING,
DEFAULT_EDITED_SUFFIX,
DEFAULT_JPEG_QUALITY,
DEFAULT_ORIGINAL_SUFFIX,
EXTENDED_ATTRIBUTE_NAMES,
EXTENDED_ATTRIBUTE_NAMES_QUOTED,
SIDECAR_EXIFTOOL,
SIDECAR_JSON,
SIDECAR_XMP,
@@ -186,7 +188,7 @@ class ExportCommand(click.Command):
formatter.write("\n")
formatter.write_text(
"""
Some options (currently '--finder-tag-template' and '--finder-tag-keywords') write
Some options (currently '--finder-tag-template', '--finder-tag-keywords', '-xattr-template') write
additional metadata to extended attributes in the file. These options will only work
if the destination filesystem supports extended attributes (most do).
For example, --finder-tag-keyword writes all keywords (including any specified by '--keyword-template'
@@ -197,9 +199,24 @@ Finder tags are written to the 'com.apple.metadata:_kMDItemUserTags' extended at
Unlike EXIF metadata, extended attributes do not modify the actual file. Most cloud storage services
do not synch extended attributes. Dropbox does sync them and any changes to a file's extended attributes
will cause Dropbox to re-sync the files.
The following attributes may be used with '--xattr-template':
"""
)
formatter.write_dl(
[
(
attr,
f"{osxmetadata.ATTRIBUTES[attr].help} ({osxmetadata.ATTRIBUTES[attr].constant})",
)
for attr in EXTENDED_ATTRIBUTE_NAMES
]
)
formatter.write("\n")
formatter.write_text(
"For additional information on extended attributes see: https://developer.apple.com/documentation/coreservices/file_metadata/mditem/common_metadata_attribute_keys"
)
formatter.write("\n\n")
formatter.write_text("** Templating System **")
formatter.write("\n")
@@ -1490,6 +1507,17 @@ def query(
help="Set MacOS Finder tags to keywords; any keywords specified via '--keyword-template', '--person-keyword', etc. "
"will also be used as Finder tags. See also '--finder-tag-template and Extended Attributes below.'.",
)
@click.option(
"--xattr-template",
nargs=2,
metavar="ATTRIBUTE TEMPLATE",
multiple=True,
help="Set extended attribute ATTRIBUTE to TEMPLATE value. Valid attributes are: "
f"{', '.join(EXTENDED_ATTRIBUTE_NAMES_QUOTED)}. "
"For example, to set Finder comment to the photo's title and description: "
"'--xattr-template findercomment \"{title}; {descr}\" "
"See Extended Attributes below for additional details on this option.",
)
@click.option(
"--directory",
metavar="DIRECTORY",
@@ -1632,6 +1660,7 @@ def export(
description_template,
finder_tag_template,
finder_tag_keywords,
xattr_template,
current_name,
convert_to_jpeg,
jpeg_quality,
@@ -1770,6 +1799,7 @@ def export(
description_template = cfg.description_template
finder_tag_template = cfg.finder_tag_template
finder_tag_keywords = cfg.finder_tag_keywords
xattr_template = cfg.xattr_template
current_name = cfg.current_name
convert_to_jpeg = cfg.convert_to_jpeg
jpeg_quality = cfg.jpeg_quality
@@ -1884,6 +1914,19 @@ def export(
)
raise click.Abort()
if xattr_template:
for attr, _ in xattr_template:
if attr not in EXTENDED_ATTRIBUTE_NAMES:
click.echo(
click.style(
f"Invalid attribute '{attr}' for --xattr-template; "
f"valid values are {', '.join(EXTENDED_ATTRIBUTE_NAMES_QUOTED)}",
fg=CLI_COLOR_ERROR,
),
err=True,
)
raise click.Abort()
if save_config:
verbose_(f"Saving options to file {save_config}")
cfg.write_to_file(save_config)
@@ -2160,18 +2203,21 @@ def export(
)
results += export_results
# all photo files (not including sidecars) that are part of this export set
# used below for applying Finder tags, etc.
photo_files = set(
export_results.exported
+ export_results.new
+ export_results.updated
+ export_results.exif_updated
+ export_results.converted_to_jpeg
+ export_results.skipped
)
if finder_tag_keywords or finder_tag_template:
files = set(
export_results.exported
+ export_results.new
+ export_results.updated
+ export_results.exif_updated
+ export_results.converted_to_jpeg
+ export_results.skipped
)
tags_written, tags_skipped = write_finder_tags(
p,
files,
photo_files,
keywords=finder_tag_keywords,
keyword_template=keyword_template,
album_keyword=album_keyword,
@@ -2182,6 +2228,13 @@ def export(
results.xattr_written.extend(tags_written)
results.xattr_skipped.extend(tags_skipped)
if xattr_template:
xattr_written, xattr_skipped = write_extended_attributes(
p, photo_files, xattr_template
)
results.xattr_written.extend(xattr_written)
results.xattr_skipped.extend(xattr_skipped)
if fp is not None:
fp.close()
@@ -3444,5 +3497,64 @@ def write_finder_tags(
return (written, skipped)
def write_extended_attributes(photo, files, xattr_template):
""" Writes extended attributes to exported files
Args:
photo: a PhotoInfo object
xattr_template: list of tuples: (attribute name, attribute template)
Returns:
tuple(list of file paths that were updated with new attributes, list of file paths skipped because attributes didn't need updating)
"""
attributes = {}
for xattr, template_str in xattr_template:
rendered, unmatched = photo.render_template(
template_str, none_str=_OSXPHOTOS_NONE_SENTINEL, path_sep="/"
)
if unmatched:
click.echo(
click.style(
f"Warning: unmatched template substitution for template: {template_str} {unmatched}",
fg=CLI_COLOR_WARNING,
),
err=True,
)
# filter out any template values that didn't match by looking for sentinel
rendered = [
value for value in rendered if _OSXPHOTOS_NONE_SENTINEL not in value
]
try:
attributes[xattr].extend(rendered)
except KeyError:
attributes[xattr] = rendered
written = set()
skipped = set()
for f in files:
md = osxmetadata.OSXMetaData(f)
for attr, value in attributes.items():
islist = osxmetadata.ATTRIBUTES[attr].list
if value:
value = ", ".join(value) if not islist else sorted(value)
file_value = md.get_attribute(attr)
if file_value and islist:
file_value = sorted(file_value)
if (not file_value and not value) or file_value == value:
# if both not set or both equal, nothing to do
# get_attribute returns None if not set and value will be [] if not set so can't directly compare
verbose_(f"Skipping extended attribute {attr} for {f}: nothing to do")
skipped.add(f)
else:
verbose_(f"Writing extended attribute {attr} to {f}")
md.set_attribute(attr, value)
written.add(f)
return list(written), [f for f in skipped if f not in written]
if __name__ == "__main__":
cli() # pylint: disable=no-value-for-parameter

View File

@@ -108,7 +108,7 @@ SEARCH_CATEGORY_NEIGHBORHOOD = 3
SEARCH_CATEGORY_LOCALITY_4 = 4
SEARCH_CATEGORY_SUB_LOCALITY_5 = 5
SEARCH_CATEGORY_SUB_LOCALITY_6 = 6
SEARCH_CATEGORY_CITY = 7
SEARCH_CATEGORY_CITY = 7
SEARCH_CATEGORY_LOCALITY_8 = 8
SEARCH_CATEGORY_NAMED_AREA = 9
SEARCH_CATEGORY_ALL_LOCALITY = [
@@ -182,4 +182,16 @@ CLI_COLOR_WARNING = "yellow"
# Bit masks for --sidecar
SIDECAR_JSON = 0x1
SIDECAR_EXIFTOOL = 0x2
SIDECAR_XMP = 0x4
SIDECAR_XMP = 0x4
# supported attributes for --xattr-template
EXTENDED_ATTRIBUTE_NAMES = [
"authors",
"comment",
"copyright",
"description",
"findercomment",
"headline",
"keywords",
]
EXTENDED_ATTRIBUTE_NAMES_QUOTED = [f"'{x}'" for x in EXTENDED_ATTRIBUTE_NAMES]

View File

@@ -1,5 +1,5 @@
""" version info """
__version__ = "0.39.1"
__version__ = "0.39.3"

View File

@@ -70,18 +70,18 @@ TEMPLATE_SUBSTITUTIONS = {
+ "{created.strftime,%Y-%U} would result in year-week number of year: '2020-23'. "
+ "If used with no template will return null value. "
+ "See https://strftime.org/ for help on strftime templates.",
"{modified.date}": "Photo's modification date in ISO format, e.g. '2020-03-22'",
"{modified.year}": "4-digit year of photo modification time",
"{modified.yy}": "2-digit year of photo modification time",
"{modified.mm}": "2-digit month of the photo modification time (zero padded)",
"{modified.month}": "Month name in user's locale of the photo modification time",
"{modified.mon}": "Month abbreviation in the user's locale of the photo modification time",
"{modified.dd}": "2-digit day of the month (zero padded) of the photo modification time",
"{modified.dow}": "Day of week in user's locale of the photo modification time",
"{modified.doy}": "3-digit day of year (e.g Julian day) of photo modification time, starting from 1 (zero padded)",
"{modified.hour}": "2-digit hour of the photo modification time",
"{modified.min}": "2-digit minute of the photo modification time",
"{modified.sec}": "2-digit second of the photo modification time",
"{modified.date}": "Photo's modification date in ISO format, e.g. '2020-03-22'; uses creation date if photo is not modified",
"{modified.year}": "4-digit year of photo modification time; uses creation date if photo is not modified",
"{modified.yy}": "2-digit year of photo modification time; uses creation date if photo is not modified",
"{modified.mm}": "2-digit month of the photo modification time (zero padded); uses creation date if photo is not modified",
"{modified.month}": "Month name in user's locale of the photo modification time; uses creation date if photo is not modified",
"{modified.mon}": "Month abbreviation in the user's locale of the photo modification time; uses creation date if photo is not modified",
"{modified.dd}": "2-digit day of the month (zero padded) of the photo modification time; uses creation date if photo is not modified",
"{modified.dow}": "Day of week in user's locale of the photo modification time; uses creation date if photo is not modified",
"{modified.doy}": "3-digit day of year (e.g Julian day) of photo modification time, starting from 1 (zero padded); uses creation date if photo is not modified",
"{modified.hour}": "2-digit hour of the photo modification time; uses creation date if photo is not modified",
"{modified.min}": "2-digit minute of the photo modification time; uses creation date if photo is not modified",
"{modified.sec}": "2-digit second of the photo modification time; uses creation date if photo is not modified",
# "{modified.strftime}": "Apply strftime template to file modification date/time. Should be used in form "
# + "{modified.strftime,TEMPLATE} where TEMPLATE is a valid strftime template, e.g. "
# + "{modified.strftime,%Y-%U} would result in year-week number of year: '2020-23'. "
@@ -679,73 +679,73 @@ class PhotoTemplate:
value = (
DateTimeFormatter(self.photo.date_modified).date
if self.photo.date_modified
else None
else DateTimeFormatter(self.photo.date).date
)
elif field == "modified.year":
value = (
DateTimeFormatter(self.photo.date_modified).year
if self.photo.date_modified
else None
else DateTimeFormatter(self.photo.date).year
)
elif field == "modified.yy":
value = (
DateTimeFormatter(self.photo.date_modified).yy
if self.photo.date_modified
else None
else DateTimeFormatter(self.photo.date).yy
)
elif field == "modified.mm":
value = (
DateTimeFormatter(self.photo.date_modified).mm
if self.photo.date_modified
else None
else DateTimeFormatter(self.photo.date).mm
)
elif field == "modified.month":
value = (
DateTimeFormatter(self.photo.date_modified).month
if self.photo.date_modified
else None
else DateTimeFormatter(self.photo.date).month
)
elif field == "modified.mon":
value = (
DateTimeFormatter(self.photo.date_modified).mon
if self.photo.date_modified
else None
else DateTimeFormatter(self.photo.date).mon
)
elif field == "modified.dd":
value = (
DateTimeFormatter(self.photo.date_modified).dd
if self.photo.date_modified
else None
else DateTimeFormatter(self.photo.date).dd
)
elif field == "modified.dow":
value = (
DateTimeFormatter(self.photo.date_modified).dow
if self.photo.date_modified
else None
else DateTimeFormatter(self.photo.date).dow
)
elif field == "modified.doy":
value = (
DateTimeFormatter(self.photo.date_modified).doy
if self.photo.date_modified
else None
else DateTimeFormatter(self.photo.date).doy
)
elif field == "modified.hour":
value = (
DateTimeFormatter(self.photo.date_modified).hour
if self.photo.date_modified
else None
else DateTimeFormatter(self.photo.date).hour
)
elif field == "modified.min":
value = (
DateTimeFormatter(self.photo.date_modified).min
if self.photo.date_modified
else None
else DateTimeFormatter(self.photo.date).min
)
elif field == "modified.sec":
value = (
DateTimeFormatter(self.photo.date_modified).sec
if self.photo.date_modified
else None
else DateTimeFormatter(self.photo.date).sec
)
elif field == "today.date":
value = DateTimeFormatter(self.today).date

View File

@@ -81,7 +81,7 @@ setup(
"wurlitzer>=2.0.1",
"photoscript>=0.1.0",
"toml>=0.10.0",
"osxmetadata>=0.99.11",
"osxmetadata>=0.99.13",
],
entry_points={"console_scripts": ["osxphotos=osxphotos.__main__:cli"]},
include_package_data=True,

File diff suppressed because one or more lines are too long

View File

@@ -5115,3 +5115,96 @@ def test_export_finder_tag_template_keywords():
persons = [persons] if type(persons) != list else persons
expected = [Tag(x) for x in keywords + persons]
assert sorted(md.tags) == sorted(expected)
def test_export_xattr_template():
""" test --xattr template """
import glob
import os
import os.path
from osxmetadata import OSXMetaData
from osxphotos.__main__ import export
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
for uuid in CLI_FINDER_TAGS:
result = runner.invoke(
export,
[
os.path.join(cwd, PHOTOS_DB_15_7),
".",
"-V",
"--xattr-template",
"keywords",
"{person}",
"--xattr-template",
"comment",
"{title}",
"--uuid",
f"{uuid}",
],
)
assert result.exit_code == 0
md = OSXMetaData(CLI_FINDER_TAGS[uuid]["File:FileName"])
expected = CLI_FINDER_TAGS[uuid]["XMP:PersonInImage"]
expected = [expected] if type(expected) != list else expected
assert sorted(md.keywords) == sorted(expected)
assert md.comment == CLI_FINDER_TAGS[uuid]["XMP:Title"]
# run again with --update, should skip writing extended attributes
result = runner.invoke(
export,
[
os.path.join(cwd, PHOTOS_DB_15_7),
".",
"-V",
"--xattr-template",
"keywords",
"{person}",
"--xattr-template",
"comment",
"{title}",
"--uuid",
f"{uuid}",
"--update",
],
)
assert result.exit_code == 0
assert "Skipping extended attribute keywords" in result.output
assert "Skipping extended attribute comment" in result.output
# clear tags and run again, should update extended attributes
md.keywords = None
md.comment = None
result = runner.invoke(
export,
[
os.path.join(cwd, PHOTOS_DB_15_7),
".",
"-V",
"--xattr-template",
"keywords",
"{person}",
"--xattr-template",
"comment",
"{title}",
"--uuid",
f"{uuid}",
"--update",
],
)
assert result.exit_code == 0
assert "Writing extended attribute keyword" in result.output
assert "Writing extended attribute comment" in result.output
md = OSXMetaData(CLI_FINDER_TAGS[uuid]["File:FileName"])
expected = CLI_FINDER_TAGS[uuid]["XMP:PersonInImage"]
expected = [expected] if type(expected) != list else expected
assert sorted(md.keywords) == sorted(expected)
assert md.comment == CLI_FINDER_TAGS[uuid]["XMP:Title"]

View File

@@ -170,17 +170,21 @@ TEMPLATE_VALUES_DATE_MODIFIED = {
}
TEMPLATE_VALUES_DATE_NOT_MODIFIED = {
# uses creation date instead of modified date
"{name}": "128FB4C6-0B16-4E7D-9108-FB2E90DA1546",
"{original_name}": "IMG_1064",
"{modified.date}": "_",
"{modified.year}": "_",
"{modified.yy}": "_",
"{modified.mm}": "_",
"{modified.month}": "_",
"{modified.mon}": "_",
"{modified.dd}": "_",
"{modified.doy}": "_",
"{modified.dow}": "_",
"{modified.date}": "2020-02-04",
"{modified.year}": "2020",
"{modified.yy}": "20",
"{modified.mm}": "02",
"{modified.month}": "February",
"{modified.mon}": "Feb",
"{modified.dd}": "04",
"{modified.dow}": "Tuesday",
"{modified.doy}": "035",
"{modified.hour}": "19",
"{modified.min}": "07",
"{modified.sec}": "38",
}