Compare commits

..

5 Commits

Author SHA1 Message Date
Rhet Turnbull
b1cb99f83f Added --exiftool-merge-keywords/persons, issue #299, #292 2020-12-28 15:31:31 -08:00
Rhet Turnbull
d3605f6303 Updated CHANGELOG.md 2020-12-28 11:58:00 -08:00
Rhet Turnbull
dce002cdfe Added --sidecar-drop-ext, issue #291 2020-12-28 11:54:02 -08:00
Rhet Turnbull
7bd189e9b2 Updated Template Substitution table 2020-12-28 09:26:38 -08:00
Rhet Turnbull
baa86c77f6 Updated CHANGELOG.md 2020-12-28 09:17:06 -08:00
9 changed files with 343 additions and 12 deletions

View File

@@ -4,6 +4,27 @@ 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.38.17](https://github.com/RhetTbull/osxphotos/compare/v0.38.16...v0.38.17)
> 28 December 2020
- Added --sidecar-drop-ext, issue #291 [`dce002c`](https://github.com/RhetTbull/osxphotos/commit/dce002cdfe12fa5fa4ada4d5097828a5375c2ecd)
- Updated Template Substitution table [`7bd189e`](https://github.com/RhetTbull/osxphotos/commit/7bd189e9b22a2ad5a8a80deb7cb93c61be37c771)
#### [v0.38.16](https://github.com/RhetTbull/osxphotos/compare/v0.38.15...v0.38.16)
> 28 December 2020
- Added searchinfo templates, issue #302 [`0d086bf`](https://github.com/RhetTbull/osxphotos/commit/0d086bf85102ce78b3111c64bfa88673fbc19559)
#### [v0.38.15](https://github.com/RhetTbull/osxphotos/compare/v0.38.14...v0.38.15)
> 28 December 2020
- Added --sidecar exiftool, issue #303 [`d833c14`](https://github.com/RhetTbull/osxphotos/commit/d833c14ef4b3f9375a85034cf0fb0f85a68cabb4)
- Refactored sidecar code [`ade98fc`](https://github.com/RhetTbull/osxphotos/commit/ade98fc15051684bfb54d0199d9c370481b70dcc)
- Refactored export2 to use sidecar bit field [`0d66759`](https://github.com/RhetTbull/osxphotos/commit/0d66759b1c200f1ecda202e28c259f88fd3db599)
#### [v0.38.14](https://github.com/RhetTbull/osxphotos/compare/v0.38.13...v0.38.14)
> 27 December 2020

View File

@@ -328,6 +328,18 @@ Options:
photoname.ext.json; For a list of tags
exported in the JSON and exiftool sidecar,
see '--exiftool'.
--sidecar-drop-ext Drop the photo's extension when naming
sidecar files. By default, sidecar files are
named in format
'photo_filename.photo_ext.sidecar_ext', e.g.
'IMG_1234.JPG.json'. Use '--sidecar-drop-
ext' to ignore the photo extension.
Resulting sidecar files will have name in
format 'IMG_1234.json'. Warning: this may
result in sidecar filename collisions if
there are files of different types but the
same name in the output directory, e.g.
'IMG_1234.JPG' and 'IMG_1234.MOV'.
--exiftool Use exiftool to write metadata directly to
exported photos. To use this option,
exiftool must be installed and in the path.
@@ -361,6 +373,12 @@ Options:
may be specified by repeating the option,
e.g. --exiftool-option '-m' --exiftool-
option '-F'.
--exiftool-merge-keywords Merge any keywords found in the original
file with keywords used for '--exiftool' and
'--sidecar'.
--exiftool-merge-persons Merge any persons found in the original file
with persons used for '--exiftool' and '--
sidecar'.
--ignore-date-modified If used with --exiftool or --sidecar, will
ignore the photo modification date and set
EXIF:ModifyDate to EXIF:DateTimeOriginal;
@@ -2244,14 +2262,19 @@ The following template field substitutions are availabe for use with `PhotoInfo.
|{place.address.postal_code}|Postal code part of the postal address, e.g. '20009'|
|{place.address.country}|Country name of the postal address, e.g. 'United States'|
|{place.address.country_code}|ISO country code of the postal address, e.g. 'US'|
|{searchinfo.season}|Season of the year associated with a photo, e.g. 'Summer'; (Photos 5+ only, applied automatically by Photos' image categorization algorithms).|
|{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|
|{keyword}|Keyword(s) assigned to photo|
|{person}|Person(s) / face(s) in a photo|
|{label}|Image categorization label associated with a photo (Photos 5 only)|
|{label_normalized}|All lower case version of 'label' (Photos 5 only)|
|{comment}|Comment(s) on shared Photos; format is 'Person name: comment text' (Photos 5 only)|
|{label}|Image categorization label associated with a photo (Photos 5+ only)|
|{label_normalized}|All lower case version of 'label' (Photos 5+ only)|
|{comment}|Comment(s) on shared Photos; format is 'Person name: comment text' (Photos 5+ only)|
|{exiftool:GROUP:TAGNAME}|Use exiftool (https://exiftool.org) to extract metadata, in form GROUP:TAGNAME, from image. E.g. '{exiftool:EXIF:Make}' to get camera make, or {exiftool:IPTC:Keywords} to extract keywords. See https://exiftool.org/TagNames/ for list of valid tag names. You must specify group (e.g. EXIF, IPTC, etc) as used in `exiftool -G`. exiftool must be installed in the path to use this template.|
|{searchinfo.holiday}|Holiday names associated with a photo, e.g. 'Christmas Day'; (Photos 5+ only, applied automatically by Photos' image categorization algorithms).|
|{searchinfo.activity}|Activities associated with a photo, e.g. 'Sporting Event'; (Photos 5+ only, applied automatically by Photos' image categorization algorithms).|
|{searchinfo.venue}|Venues associated with a photo, e.g. name of restaurant; (Photos 5+ only, applied automatically by Photos' image categorization algorithms).|
|{searchinfo.venue_type}|Venue types associated with a photo, e.g. 'Restaurant'; (Photos 5+ only, applied automatically by Photos' image categorization algorithms).|
### Utility Functions

View File

@@ -1357,6 +1357,16 @@ def query(
"Sidecar filename is in format photoname.ext.json; "
"For a list of tags exported in the JSON and exiftool sidecar, see '--exiftool'.",
)
@click.option(
"--sidecar-drop-ext",
is_flag=True,
help="Drop the photo's extension when naming sidecar files. "
"By default, sidecar files are named in format 'photo_filename.photo_ext.sidecar_ext', "
"e.g. 'IMG_1234.JPG.json'. Use '--sidecar-drop-ext' to ignore the photo extension. "
"Resulting sidecar files will have name in format 'IMG_1234.json'. "
"Warning: this may result in sidecar filename collisions if there are files of different "
"types but the same name in the output directory, e.g. 'IMG_1234.JPG' and 'IMG_1234.MOV'.",
)
@click.option(
"--exiftool",
is_flag=True,
@@ -1366,7 +1376,7 @@ def query(
"Cannot be used with --export-as-hardlink. Writes the following metadata: "
"EXIF:ImageDescription, XMP:Description (see also --description-template); "
"XMP:Title; XMP:TagsList, IPTC:Keywords, XMP:Subject "
"(see also --keyword-template, --person-keyword, --album-keyword); "
"(see also --keyword-template, --person-keyword, --album-keyword); "
"XMP:PersonInImage; EXIF:GPSLatitudeRef; EXIF:GPSLongitudeRef; EXIF:GPSLatitude; EXIF:GPSLongitude; "
"EXIF:GPSPosition; EXIF:DateTimeOriginal; EXIF:OffsetTimeOriginal; "
"EXIF:ModifyDate (see --ignore-date-modified); IPTC:DateCreated; IPTC:TimeCreated; "
@@ -1384,6 +1394,16 @@ def query(
"More than one option may be specified by repeating the option, e.g. "
"--exiftool-option '-m' --exiftool-option '-F'. ",
)
@click.option(
"--exiftool-merge-keywords",
is_flag=True,
help="Merge any keywords found in the original file with keywords used for '--exiftool' and '--sidecar'.",
)
@click.option(
"--exiftool-merge-persons",
is_flag=True,
help="Merge any persons found in the original file with persons used for '--exiftool' and '--sidecar'.",
)
@click.option(
"--ignore-date-modified",
is_flag=True,
@@ -1571,6 +1591,7 @@ def export(
convert_to_jpeg,
jpeg_quality,
sidecar,
sidecar_drop_ext,
only_photos,
only_movies,
burst,
@@ -1581,6 +1602,8 @@ def export(
dest,
exiftool,
exiftool_option,
exiftool_merge_keywords,
exiftool_merge_persons,
ignore_date_modified,
portrait,
not_portrait,
@@ -1703,6 +1726,7 @@ def export(
convert_to_jpeg = cfg.convert_to_jpeg
jpeg_quality = cfg.jpeg_quality
sidecar = cfg.sidecar
sidecar_drop_ext = cfg.sidecar_drop_ext
only_photos = cfg.only_photos
only_movies = cfg.only_movies
burst = cfg.burst
@@ -1711,6 +1735,9 @@ def export(
not_live = cfg.not_live
download_missing = cfg.download_missing
exiftool = cfg.exiftool
exiftool_option = cfg.exiftool_option
exiftool_merge_keywords = cfg.exiftool_merge_keywords
exiftool_merge_persons = cfg.exiftool_merge_persons
ignore_date_modified = cfg.ignore_date_modified
portrait = cfg.portrait
not_portrait = cfg.not_portrait
@@ -1784,6 +1811,8 @@ def export(
("jpeg_quality", ("convert_to_jpeg")),
("ignore_signature", ("update")),
("exiftool_option", ("exiftool")),
("exiftool_merge_keywords", ("exiftool", "sidecar")),
("exiftool_merge_persons", ("exiftool", "sidecar")),
]
try:
cfg.validate(exclusive=exclusive_options, dependent=dependent_options, cli=True)
@@ -2036,6 +2065,7 @@ def export(
verbose=verbose,
export_by_date=export_by_date,
sidecar=sidecar,
sidecar_drop_ext=sidecar_drop_ext,
update=update,
ignore_signature=ignore_signature,
export_as_hardlink=export_as_hardlink,
@@ -2046,6 +2076,8 @@ def export(
export_live=export_live,
download_missing=download_missing,
exiftool=exiftool,
exiftool_merge_keywords=exiftool_merge_keywords,
exiftool_merge_persons=exiftool_merge_persons,
directory=directory,
filename_template=filename_template,
export_raw=export_raw,
@@ -2084,6 +2116,7 @@ def export(
verbose=verbose,
export_by_date=export_by_date,
sidecar=sidecar,
sidecar_drop_ext=sidecar_drop_ext,
update=update,
ignore_signature=ignore_signature,
export_as_hardlink=export_as_hardlink,
@@ -2094,6 +2127,8 @@ def export(
export_live=export_live,
download_missing=download_missing,
exiftool=exiftool,
exiftool_merge_keywords=exiftool_merge_keywords,
exiftool_merge_persons=exiftool_merge_persons,
directory=directory,
filename_template=filename_template,
export_raw=export_raw,
@@ -2616,6 +2651,7 @@ def export_photo(
verbose=None,
export_by_date=None,
sidecar=None,
sidecar_drop_ext=False,
update=None,
ignore_signature=None,
export_as_hardlink=None,
@@ -2626,6 +2662,8 @@ def export_photo(
export_live=None,
download_missing=None,
exiftool=None,
exiftool_merge_keywords=False,
exiftool_merge_persons=False,
directory=None,
filename_template=None,
export_raw=None,
@@ -2654,6 +2692,7 @@ def export_photo(
verbose: boolean; print verbose output
export_by_date: boolean; create export folder in form dest/YYYY/MM/DD
sidecar: list zero, 1 or 2 of ["json","xmp"] of sidecar variety to export
sidecar_drop_ext: boolean; if True, drops photo extension from sidecar name
export_as_hardlink: boolean; hardlink files instead of copying them
overwrite: boolean; overwrite dest file if it already exists
original_name: boolean; use original filename instead of current filename
@@ -2679,6 +2718,8 @@ def export_photo(
jpeg_quality: float in range 0.0 <= jpeg_quality <= 1.0. A value of 1.0 specifies use best quality, a value of 0.0 specifies use maximum compression.
ignore_date_modified: if True, sets EXIF:ModifyDate to EXIF:DateTimeOriginal even if date_modified is set
exiftool_option: optional list flags (e.g. ["-m", "-F"]) to pass to exiftool
exiftool_merge_keywords: boolean; if True, merged keywords found in file's exif data (requires exiftool)
exiftool_merge_persons: boolean; if True, merged persons found in file's exif data (requires exiftool)
Returns:
list of path(s) of exported photo or None if photo was missing
@@ -2804,12 +2845,15 @@ def export_photo(
dest_path,
original_filename,
sidecar=sidecar_flags,
sidecar_drop_ext=sidecar_drop_ext,
live_photo=export_live,
raw_photo=export_raw,
export_as_hardlink=export_as_hardlink,
overwrite=overwrite,
use_photos_export=use_photos_export,
exiftool=exiftool,
merge_exif_keywords=exiftool_merge_keywords,
merge_exif_persons=exiftool_merge_persons,
use_albums_as_keywords=album_keyword,
use_persons_as_keywords=person_keyword,
keyword_template=keyword_template,
@@ -2908,11 +2952,14 @@ def export_photo(
dest_path,
edited_filename,
sidecar=sidecar_flags,
sidecar_drop_ext=sidecar_drop_ext,
export_as_hardlink=export_as_hardlink,
overwrite=overwrite,
edited=True,
use_photos_export=use_photos_export,
exiftool=exiftool,
merge_exif_keywords=exiftool_merge_keywords,
merge_exif_persons=exiftool_merge_persons,
use_albums_as_keywords=album_keyword,
use_persons_as_keywords=person_keyword,
keyword_template=keyword_template,

View File

@@ -1,5 +1,5 @@
""" version info """
__version__ = "0.38.16"
__version__ = "0.38.18"

View File

@@ -5,6 +5,8 @@
_export_photo
_write_exif_data
_exiftool_json_sidecar
_get_exif_keywords
_get_exif_persons
_exiftool_dict
_xmp_sidecar
_write_sidecar
@@ -430,6 +432,7 @@ def export2(
overwrite=False,
increment=True,
sidecar=0,
sidecar_drop_ext=False,
use_photos_export=False,
timeout=120,
exiftool=False,
@@ -449,6 +452,8 @@ def export2(
use_photokit=False,
verbose=None,
exiftool_flags=None,
merge_exif_keywords=False,
merge_exif_persons=False,
):
"""export photo, like export but with update and dry_run options
dest: must be valid destination path or exception raised
@@ -475,6 +480,7 @@ def export2(
sidecar filename will be dest/filename.json; does not include exiftool tag group names (e.g. `exiftool -j`)
SIDECAR_XMP: if set will write an XMP sidecar with IPTC data
sidecar filename will be dest/filename.xmp
sidecar_drop_ext: (boolean, default=False); if True, drops the photo's extension from sidecar filename (e.g. 'IMG_1234.json' instead of 'IMG_1234.JPG.json')
use_photos_export: (boolean, default=False); if True will attempt to export photo via applescript interaction with Photos
timeout: (int, default=120) timeout in seconds used with use_photos_export
exiftool: (boolean, default = False); if True, will use exiftool to write metadata to export file
@@ -497,6 +503,8 @@ def export2(
ignore_date_modified: for use with sidecar and exiftool; if True, sets EXIF:ModifyDate to EXIF:DateTimeOriginal even if date_modified is set
verbose: optional callable function to use for printing verbose text during processing; if None (default), does not print output.
exiftool_flags: optional list of flags to pass to exiftool when using exiftool option, e.g ["-m", "-F"]
merge_exif_keywords: boolean; if True, merged keywords found in file's exif data (requires exiftool)
merge_exif_persons: boolean; if True, merged persons found in file's exif data (requires exiftool)
Returns: ExportResults class
ExportResults has attributes:
@@ -893,14 +901,17 @@ def export2(
sidecar_xmp_files_skipped = []
sidecar_xmp_files_written = []
dest_suffix = "" if sidecar_drop_ext else dest.suffix
if sidecar & SIDECAR_JSON:
sidecar_filename = dest.parent / pathlib.Path(f"{dest.stem}{dest.suffix}.json")
sidecar_filename = dest.parent / pathlib.Path(f"{dest.stem}{dest_suffix}.json")
sidecar_str = self._exiftool_json_sidecar(
use_albums_as_keywords=use_albums_as_keywords,
use_persons_as_keywords=use_persons_as_keywords,
keyword_template=keyword_template,
description_template=description_template,
ignore_date_modified=ignore_date_modified,
merge_exif_keywords=merge_exif_keywords,
merge_exif_persons=merge_exif_persons,
)
sidecars.append(
(
@@ -913,7 +924,7 @@ def export2(
)
if sidecar & SIDECAR_EXIFTOOL:
sidecar_filename = dest.parent / pathlib.Path(f"{dest.stem}{dest.suffix}.json")
sidecar_filename = dest.parent / pathlib.Path(f"{dest.stem}{dest_suffix}.json")
sidecar_str = self._exiftool_json_sidecar(
use_albums_as_keywords=use_albums_as_keywords,
use_persons_as_keywords=use_persons_as_keywords,
@@ -921,6 +932,8 @@ def export2(
description_template=description_template,
ignore_date_modified=ignore_date_modified,
tag_groups=False,
merge_exif_keywords=merge_exif_keywords,
merge_exif_persons=merge_exif_persons,
)
sidecars.append(
(
@@ -933,7 +946,7 @@ def export2(
)
if sidecar & SIDECAR_XMP:
sidecar_filename = dest.parent / pathlib.Path(f"{dest.stem}{dest.suffix}.xmp")
sidecar_filename = dest.parent / pathlib.Path(f"{dest.stem}{dest_suffix}.xmp")
sidecar_str = self._xmp_sidecar(
use_albums_as_keywords=use_albums_as_keywords,
use_persons_as_keywords=use_persons_as_keywords,
@@ -1009,6 +1022,8 @@ def export2(
keyword_template=keyword_template,
description_template=description_template,
ignore_date_modified=ignore_date_modified,
merge_exif_keywords=merge_exif_keywords,
merge_exif_persons=merge_exif_persons,
)
)[0]
if old_data != current_data:
@@ -1027,6 +1042,8 @@ def export2(
description_template=description_template,
ignore_date_modified=ignore_date_modified,
flags=exiftool_flags,
merge_exif_keywords=merge_exif_keywords,
merge_exif_persons=merge_exif_persons,
)
if warning_:
exiftool_warning.append((exported_file, warning_))
@@ -1042,6 +1059,8 @@ def export2(
keyword_template=keyword_template,
description_template=description_template,
ignore_date_modified=ignore_date_modified,
merge_exif_keywords=merge_exif_keywords,
merge_exif_persons=merge_exif_persons,
),
)
export_db.set_stat_exif_for_file(
@@ -1062,6 +1081,8 @@ def export2(
description_template=description_template,
ignore_date_modified=ignore_date_modified,
flags=exiftool_flags,
merge_exif_keywords=merge_exif_keywords,
merge_exif_persons=merge_exif_persons,
)
if warning_:
exiftool_warning.append((exported_file, warning_))
@@ -1077,6 +1098,8 @@ def export2(
keyword_template=keyword_template,
description_template=description_template,
ignore_date_modified=ignore_date_modified,
merge_exif_keywords=merge_exif_keywords,
merge_exif_persons=merge_exif_persons,
),
)
export_db.set_stat_exif_for_file(
@@ -1305,6 +1328,8 @@ def _write_exif_data(
description_template=None,
ignore_date_modified=False,
flags=None,
merge_exif_keywords=False,
merge_exif_persons=False,
):
"""write exif data to image file at filepath
@@ -1327,6 +1352,8 @@ def _write_exif_data(
keyword_template=keyword_template,
description_template=description_template,
ignore_date_modified=ignore_date_modified,
merge_exif_keywords=merge_exif_keywords,
merge_exif_persons=merge_exif_persons,
)
with ExifTool(filepath, flags=flags) as exiftool:
@@ -1346,6 +1373,8 @@ def _exiftool_dict(
keyword_template=None,
description_template=None,
ignore_date_modified=False,
merge_exif_keywords=False,
merge_exif_persons=False,
):
"""Return dict of EXIF details for building exiftool JSON sidecar or sending commands to ExifTool.
Does not include all the EXIF fields as those are likely already in the image.
@@ -1356,6 +1385,8 @@ def _exiftool_dict(
keyword_template: (list of strings); list of template strings to render as keywords
description_template: (list of strings); list of template strings to render for the description
ignore_date_modified: if True, sets EXIF:ModifyDate to EXIF:DateTimeOriginal even if date_modified is set
merge_exif_keywords: merge keywords in the file's exif metadata (requires exiftool)
merge_exif_persons: merge persons in the file's exif metadata (requires exiftool)
Returns: dict with exiftool tags / values
@@ -1398,13 +1429,19 @@ def _exiftool_dict(
exif["XMP:Title"] = self.title
keyword_list = []
if merge_exif_keywords:
keyword_list.extend(self._get_exif_keywords())
if self.keywords:
keyword_list.extend(self.keywords)
person_list = []
if merge_exif_persons:
person_list.extend(self._get_exif_persons())
if self.persons:
# filter out _UNKNOWN_PERSON
person_list = [p for p in self.persons if p != _UNKNOWN_PERSON]
person_list.extend([p for p in self.persons if p != _UNKNOWN_PERSON])
if use_persons_as_keywords and person_list:
keyword_list.extend(person_list)
@@ -1531,6 +1568,39 @@ def _exiftool_dict(
return exif
def _get_exif_keywords(self):
""" returns list of keywords found in the file's exif metadata """
keywords = []
exif = self.exiftool
if exif:
exifdict = exif.asdict()
for field in ["IPTC:Keywords", "XMP:TagsList", "XMP:Subject"]:
try:
kw = exifdict[field]
if kw and type(kw) != list:
kw = [kw]
keywords.extend(kw)
except KeyError:
pass
return keywords
def _get_exif_persons(self):
""" returns list of persons found in the file's exif metadata """
persons = []
exif = self.exiftool
if exif:
exifdict = exif.asdict()
try:
p = exifdict["XMP:PersonInImage"]
if p and type(p) != list:
p = [p]
persons.extend(p)
except KeyError:
pass
return persons
def _exiftool_json_sidecar(
self,
use_albums_as_keywords=False,
@@ -1539,6 +1609,8 @@ def _exiftool_json_sidecar(
description_template=None,
ignore_date_modified=False,
tag_groups=True,
merge_exif_keywords=False,
merge_exif_persons=False,
):
"""Return dict of EXIF details for building exiftool JSON sidecar or sending commands to ExifTool.
Does not include all the EXIF fields as those are likely already in the image.
@@ -1550,6 +1622,8 @@ def _exiftool_json_sidecar(
description_template: (list of strings); list of template strings to render for the description
ignore_date_modified: if True, sets EXIF:ModifyDate to EXIF:DateTimeOriginal even if date_modified is set
tag_groups: if True, tags are in form Group:TagName, e.g. IPTC:Keywords, otherwise group name is omitted, e.g. Keywords
merge_exif_keywords: boolean; if True, merged keywords found in file's exif data (requires exiftool)
merge_exif_persons: boolean; if True, merged persons found in file's exif data (requires exiftool)
Returns: dict with exiftool tags / values
@@ -1581,6 +1655,8 @@ def _exiftool_json_sidecar(
keyword_template=keyword_template,
description_template=description_template,
ignore_date_modified=ignore_date_modified,
merge_exif_keywords=merge_exif_keywords,
merge_exif_persons=merge_exif_persons,
)
if not tag_groups:
@@ -1601,12 +1677,17 @@ def _xmp_sidecar(
keyword_template=None,
description_template=None,
extension=None,
merge_exif_keywords=False,
merge_exif_persons=False,
):
"""returns string for XMP sidecar
use_albums_as_keywords: treat album names as keywords
use_persons_as_keywords: treat person names as keywords
keyword_template: (list of strings); list of template strings to render 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
merge_exif_keywords: boolean; if True, merged keywords found in file's exif data (requires exiftool)
merge_exif_persons: boolean; if True, merged persons found in file's exif data (requires exiftool)
"""
xmp_template = Template(filename=os.path.join(_TEMPLATE_DIR, _XMP_TEMPLATE_NAME))
@@ -1623,6 +1704,9 @@ def _xmp_sidecar(
description = self.description if self.description is not None else ""
keyword_list = []
if merge_exif_keywords:
keyword_list.extend(self._get_exif_keywords())
if self.keywords:
keyword_list.extend(self.keywords)
@@ -1630,9 +1714,12 @@ def _xmp_sidecar(
# good candidate for pulling out in a function
person_list = []
if merge_exif_persons:
person_list.extend(self._get_exif_persons())
if self.persons:
# filter out _UNKNOWN_PERSON
person_list = [p for p in self.persons if p != _UNKNOWN_PERSON]
person_list.extend([p for p in self.persons if p != _UNKNOWN_PERSON])
if use_persons_as_keywords and person_list:
keyword_list.extend(person_list)

View File

@@ -21,11 +21,11 @@ from .._constants import (
_PHOTOS_4_ALBUM_KIND,
_PHOTOS_4_ROOT_FOLDER,
_PHOTOS_4_VERSION,
_PHOTOS_5_VERSION,
_PHOTOS_5_ALBUM_KIND,
_PHOTOS_5_IMPORT_SESSION_ALBUM_KIND,
_PHOTOS_5_SHARED_ALBUM_KIND,
_PHOTOS_5_SHARED_PHOTO_PATH,
_PHOTOS_5_VERSION,
)
from ..albuminfo import AlbumInfo, ImportInfo
from ..personinfo import FaceInfo, PersonInfo
@@ -56,6 +56,8 @@ class PhotoInfo:
_export_photo,
_exiftool_dict,
_exiftool_json_sidecar,
_get_exif_keywords,
_get_exif_persons,
_write_exif_data,
_write_sidecar,
_xmp_sidecar,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 574 KiB

After

Width:  |  Height:  |  Size: 577 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 528 KiB

After

Width:  |  Height:  |  Size: 532 KiB

View File

@@ -306,6 +306,11 @@ CLI_EXPORT_BY_DATE_NEED_TOUCH_TIMES = [1538165227, 1539436692]
CLI_EXPORT_BY_DATE = ["2018/09/28/Pumpkins3.jpg", "2018/09/28/Pumkins1.jpg"]
CLI_EXPORT_SIDECAR_FILENAMES = ["Pumkins2.jpg", "Pumkins2.jpg.json", "Pumkins2.jpg.xmp"]
CLI_EXPORT_SIDECAR_DROP_EXT_FILENAMES = [
"Pumkins2.jpg",
"Pumkins2.json",
"Pumkins2.xmp",
]
CLI_EXPORT_LIVE = [
"51F2BEF7-431A-4D31-8AC1-3284A57826AE.jpeg",
@@ -362,6 +367,29 @@ CLI_EXIFTOOL = {
}
}
CLI_EXIFTOOL_MERGE = {
"1EB2B765-0765-43BA-A90C-0D0580E6172C": {
"File:FileName": "Pumpkins3.jpg",
"IPTC:Keywords": "Kids",
"XMP:TagsList": "Kids",
"EXIF:ImageDescription": "Kids in pumpkin field",
"XMP:Description": "Kids in pumpkin field",
"XMP:PersonInImage": ["Katie", "Suzy", "Tim"],
"XMP:Subject": "Kids",
},
"D79B8D77-BFFC-460B-9312-034F2877D35B": {
"File:FileName": "Pumkins2.jpg",
"XMP:Title": "I found one!",
"EXIF:ImageDescription": "Girl holding pumpkin",
"XMP:Description": "Girl holding pumpkin",
"XMP:PersonInImage": "Katie",
"IPTC:Keywords": ["Kids", "keyword1", "keyword2", "subject1", "tagslist1"],
"XMP:TagsList": ["Kids", "keyword1", "keyword2", "subject1", "tagslist1"],
"XMP:Subject": ["Kids", "keyword1", "keyword2", "subject1", "tagslist1"],
},
}
CLI_EXIFTOOL_QUICKTIME = {
"35329C57-B963-48D6-BB75-6AFF9370CBBC": {
"File:FileName": "Jellyfish.MOV",
@@ -1211,6 +1239,96 @@ def test_export_exiftool_option():
assert "exiftool warning" not in result.output
@pytest.mark.skipif(exiftool is None, reason="exiftool not installed")
def test_export_exiftool_merge():
""" test --exiftool-merge-keywords and --exiftool-merge-persons """
import glob
import os
import os.path
from osxphotos.__main__ import export
from osxphotos.exiftool import ExifTool
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
for uuid in CLI_EXIFTOOL_MERGE:
result = runner.invoke(
export,
[
os.path.join(cwd, PHOTOS_DB_15_7),
".",
"-V",
"--exiftool",
"--uuid",
f"{uuid}",
"--exiftool-merge-keywords",
"--exiftool-merge-persons",
],
)
assert result.exit_code == 0
files = glob.glob("*")
assert CLI_EXIFTOOL_MERGE[uuid]["File:FileName"] in files
exif = ExifTool(CLI_EXIFTOOL_MERGE[uuid]["File:FileName"]).asdict()
for key in CLI_EXIFTOOL_MERGE[uuid]:
if type(exif[key]) == list:
assert sorted(exif[key]) == sorted(CLI_EXIFTOOL_MERGE[uuid][key])
else:
assert exif[key] == CLI_EXIFTOOL_MERGE[uuid][key]
@pytest.mark.skipif(exiftool is None, reason="exiftool not installed")
def test_export_exiftool_merge_sidecar():
""" test --exiftool-merge-keywords and --exiftool-merge-persons with --sidecar """
import glob
import json
import os
import os.path
from osxphotos.__main__ import export
from osxphotos.exiftool import ExifTool
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
for uuid in CLI_EXIFTOOL_MERGE:
result = runner.invoke(
export,
[
os.path.join(cwd, PHOTOS_DB_15_7),
".",
"-V",
"--sidecar",
"json",
"--uuid",
f"{uuid}",
"--exiftool-merge-keywords",
"--exiftool-merge-persons",
],
)
assert result.exit_code == 0
files = glob.glob("*")
json_file = f"{CLI_EXIFTOOL_MERGE[uuid]['File:FileName']}.json"
assert json_file in files
with open(json_file, "r") as fp:
exif = json.load(fp)[0]
for key in CLI_EXIFTOOL_MERGE[uuid]:
if key == "File:FileName":
continue
if type(exif[key]) == list:
expected = (
CLI_EXIFTOOL_MERGE[uuid][key]
if type(CLI_EXIFTOOL_MERGE[uuid][key]) == list
else [CLI_EXIFTOOL_MERGE[uuid][key]]
)
assert sorted(exif[key]) == sorted(expected)
else:
assert exif[key] == CLI_EXIFTOOL_MERGE[uuid][key]
def test_export_edited_suffix():
""" test export with --edited-suffix """
import glob
@@ -1974,6 +2092,7 @@ def test_query_deleted_4():
def test_export_sidecar():
""" test --sidecar """
import glob
import os
import os.path
@@ -2003,6 +2122,38 @@ def test_export_sidecar():
assert sorted(files) == sorted(CLI_EXPORT_SIDECAR_FILENAMES)
def test_export_sidecar_drop_ext():
""" test --sidecar with --sidecar-drop-ext option """
import glob
import os
import os.path
import osxphotos
from osxphotos.__main__ import cli
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
result = runner.invoke(
cli,
[
"export",
"--db",
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"--sidecar=json",
"--sidecar=xmp",
"--sidecar-drop-ext",
f"--uuid={CLI_EXPORT_UUID}",
"-V",
],
)
assert result.exit_code == 0
files = glob.glob("*.*")
assert sorted(files) == sorted(CLI_EXPORT_SIDECAR_DROP_EXT_FILENAMES)
def test_export_sidecar_exiftool():
""" test --sidecar exiftool """
import glob