From 4b7a53faa8d7ff2e941e7653554f61bcbd416fc9 Mon Sep 17 00:00:00 2001 From: Rhet Turnbull Date: Sat, 13 Feb 2021 10:37:01 -0800 Subject: [PATCH] Write description to ITPC:CaptionAbstract (#380) --- osxphotos/_version.py | 2 +- osxphotos/photoinfo/_photoinfo_export.py | 113 +++++++++++++----- osxphotos/templates/xmp_sidecar.mako | 2 +- tests/search_info_test_data_10_15_7.json | 2 +- tests/sidecars/15uNd7%8RguTEgNPKHfTWw.json | 2 +- ...d7%8RguTEgNPKHfTWw_albums_as_keywords.json | 2 +- ...%8RguTEgNPKHfTWw_ignore_date_modified.json | 2 +- ...uNd7%8RguTEgNPKHfTWw_keyword_template.json | 2 +- .../15uNd7%8RguTEgNPKHfTWw_no_tag_groups.json | 2 +- ...7%8RguTEgNPKHfTWw_persons_as_keywords.json | 2 +- tests/sidecars/3Jn73XpSQQCluzRBMWRsMA.json | 2 +- ...3XpSQQCluzRBMWRsMA_albums_as_keywords.json | 2 +- ...pSQQCluzRBMWRsMA_ignore_date_modified.json | 2 +- ...n73XpSQQCluzRBMWRsMA_keyword_template.json | 2 +- .../3Jn73XpSQQCluzRBMWRsMA_no_tag_groups.json | 2 +- ...XpSQQCluzRBMWRsMA_persons_as_keywords.json | 2 +- .../6191423D-8DB8-4D4C-92BE-9BBBA308AAC4.json | 2 +- ...-92BE-9BBBA308AAC4_albums_as_keywords.json | 2 +- ...2BE-9BBBA308AAC4_ignore_date_modified.json | 2 +- ...4C-92BE-9BBBA308AAC4_keyword_template.json | 2 +- ...-4D4C-92BE-9BBBA308AAC4_no_tag_groups.json | 2 +- ...92BE-9BBBA308AAC4_persons_as_keywords.json | 2 +- tests/sidecars/6bxcNnzRQKGnK4uPrCJ9UQ.json | 2 +- ...NnzRQKGnK4uPrCJ9UQ_albums_as_keywords.json | 2 +- ...zRQKGnK4uPrCJ9UQ_ignore_date_modified.json | 2 +- ...xcNnzRQKGnK4uPrCJ9UQ_keyword_template.json | 2 +- .../6bxcNnzRQKGnK4uPrCJ9UQ_no_tag_groups.json | 2 +- ...nzRQKGnK4uPrCJ9UQ_persons_as_keywords.json | 2 +- tests/sidecars/8SOE9s0XQVGsuq4ONohTng.json | 2 +- ...9s0XQVGsuq4ONohTng_albums_as_keywords.json | 2 +- ...0XQVGsuq4ONohTng_ignore_date_modified.json | 2 +- ...OE9s0XQVGsuq4ONohTng_keyword_template.json | 2 +- .../8SOE9s0XQVGsuq4ONohTng_no_tag_groups.json | 2 +- ...s0XQVGsuq4ONohTng_persons_as_keywords.json | 2 +- .../A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C.json | 2 +- ...-9AC9-5AFEFE2D3A5C_albums_as_keywords.json | 2 +- ...AC9-5AFEFE2D3A5C_ignore_date_modified.json | 2 +- ...1F-9AC9-5AFEFE2D3A5C_keyword_template.json | 2 +- ...-431F-9AC9-5AFEFE2D3A5C_no_tag_groups.json | 2 +- ...9AC9-5AFEFE2D3A5C_persons_as_keywords.json | 2 +- .../D79B8D77-BFFC-460B-9312-034F2877D35B.json | 2 +- ...-9312-034F2877D35B_albums_as_keywords.json | 2 +- ...312-034F2877D35B_ignore_date_modified.json | 2 +- ...0B-9312-034F2877D35B_keyword_template.json | 2 +- ...-460B-9312-034F2877D35B_no_tag_groups.json | 2 +- ...9312-034F2877D35B_persons_as_keywords.json | 2 +- .../DC99FBDD-7A52-4100-A5BB-344131646C30.json | 2 +- ...-A5BB-344131646C30_albums_as_keywords.json | 2 +- ...5BB-344131646C30_ignore_date_modified.json | 2 +- ...00-A5BB-344131646C30_keyword_template.json | 2 +- ...-4100-A5BB-344131646C30_no_tag_groups.json | 2 +- ...A5BB-344131646C30_persons_as_keywords.json | 2 +- .../E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51.json | 2 +- ...-A72B-8B8FAC227D51_albums_as_keywords.json | 2 +- ...72B-8B8FAC227D51_ignore_date_modified.json | 2 +- ...A1-A72B-8B8FAC227D51_keyword_template.json | 2 +- ...-40A1-A72B-8B8FAC227D51_no_tag_groups.json | 2 +- ...A72B-8B8FAC227D51_persons_as_keywords.json | 2 +- .../F12384F6-CD17-4151-ACBA-AE0E3688539E.json | 2 +- ...-ACBA-AE0E3688539E_albums_as_keywords.json | 2 +- ...CBA-AE0E3688539E_ignore_date_modified.json | 2 +- ...51-ACBA-AE0E3688539E_keyword_template.json | 2 +- ...-4151-ACBA-AE0E3688539E_no_tag_groups.json | 2 +- ...ACBA-AE0E3688539E_persons_as_keywords.json | 2 +- tests/test_cli.py | 4 +- tests/test_export_catalina_10_15_7.py | 1 + 66 files changed, 152 insertions(+), 92 deletions(-) diff --git a/osxphotos/_version.py b/osxphotos/_version.py index 733a8fb0..272790b3 100644 --- a/osxphotos/_version.py +++ b/osxphotos/_version.py @@ -1,3 +1,3 @@ """ version info """ -__version__ = "0.40.14" +__version__ = "0.40.15" diff --git a/osxphotos/photoinfo/_photoinfo_export.py b/osxphotos/photoinfo/_photoinfo_export.py index 4f681e16..2a966b9f 100644 --- a/osxphotos/photoinfo/_photoinfo_export.py +++ b/osxphotos/photoinfo/_photoinfo_export.py @@ -475,6 +475,8 @@ def export2( merge_exif_keywords=False, merge_exif_persons=False, jpeg_ext=None, + persons=True, + location=True, ): """export photo, like export but with update and dry_run options dest: must be valid destination path or exception raised @@ -527,6 +529,8 @@ def export2( 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) jpeg_ext: if set, will use this value for extension on jpegs converted to jpeg with convert_to_jpeg; if not set, uses jpeg; do not include the leading "." + persons: if True, include persons in exported metadata + location: if True, include location in exported metadata Returns: ExportResults class ExportResults has attributes: @@ -941,6 +945,8 @@ def export2( merge_exif_keywords=merge_exif_keywords, merge_exif_persons=merge_exif_persons, filename=dest.name, + persons=persons, + location=location, ) sidecars.append( ( @@ -964,6 +970,8 @@ def export2( merge_exif_keywords=merge_exif_keywords, merge_exif_persons=merge_exif_persons, filename=dest.name, + persons=persons, + location=location, ) sidecars.append( ( @@ -983,6 +991,8 @@ def export2( keyword_template=keyword_template, description_template=description_template, extension=dest.suffix[1:] if dest.suffix else None, + persons=persons, + location=location, ) sidecars.append( ( @@ -1050,6 +1060,8 @@ def export2( ignore_date_modified=ignore_date_modified, merge_exif_keywords=merge_exif_keywords, merge_exif_persons=merge_exif_persons, + persons=persons, + location=location, ) )[0] if old_data != current_data: @@ -1070,6 +1082,8 @@ def export2( flags=exiftool_flags, merge_exif_keywords=merge_exif_keywords, merge_exif_persons=merge_exif_persons, + persons=persons, + location=location, ) if warning_: all_results.exiftool_warning.append((exported_file, warning_)) @@ -1087,6 +1101,8 @@ def export2( ignore_date_modified=ignore_date_modified, merge_exif_keywords=merge_exif_keywords, merge_exif_persons=merge_exif_persons, + persons=persons, + location=location, ), ) export_db.set_stat_exif_for_file( @@ -1109,6 +1125,8 @@ def export2( flags=exiftool_flags, merge_exif_keywords=merge_exif_keywords, merge_exif_persons=merge_exif_persons, + persons=persons, + location=location, ) if warning_: all_results.exiftool_warning.append((exported_file, warning_)) @@ -1126,6 +1144,8 @@ def export2( ignore_date_modified=ignore_date_modified, merge_exif_keywords=merge_exif_keywords, merge_exif_persons=merge_exif_persons, + persons=persons, + location=location, ), ) export_db.set_stat_exif_for_file( @@ -1345,6 +1365,8 @@ def _write_exif_data( flags=None, merge_exif_keywords=False, merge_exif_persons=False, + persons=True, + location=True, ): """write exif data to image file at filepath @@ -1355,6 +1377,8 @@ def _write_exif_data( keyword_template: (list of strings); list of template strings to render as keywords ignore_date_modified: if True, sets EXIF:ModifyDate to EXIF:DateTimeOriginal even if date_modified is set flags: optional list of exiftool flags to prepend to exiftool command when writing metadata (e.g. -m or -F) + persons: if True, write person data to metadata + location: if True, write location data to metadata Returns: (warning, error) of warning and error strings if exiftool produces warnings or errors @@ -1369,6 +1393,8 @@ def _write_exif_data( ignore_date_modified=ignore_date_modified, merge_exif_keywords=merge_exif_keywords, merge_exif_persons=merge_exif_persons, + persons=persons, + location=location, ) with ExifTool(filepath, flags=flags, exiftool=self._db._exiftool_path) as exiftool: @@ -1391,6 +1417,8 @@ def _exiftool_dict( merge_exif_keywords=False, merge_exif_persons=False, filename=None, + persons=True, + location=True, ): """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. @@ -1404,6 +1432,8 @@ def _exiftool_dict( 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) + persons: if True, include person data + location: if True, include location data Returns: dict with exiftool tags / values @@ -1411,8 +1441,10 @@ def _exiftool_dict( EXIF:ImageDescription (may include template) XMP:Description (may include template) XMP:Title + IPTC:ObjectName XMP:TagsList (may include album name, person name, or template) IPTC:Keywords (may include album name, person name, or template) + IPTC:Caption-Abstract XMP:Subject (set to keywords + persons) XMP:PersonInImage EXIF:GPSLatitudeRef, EXIF:GPSLongitudeRef @@ -1428,6 +1460,9 @@ def _exiftool_dict( QuickTime:ModifyDate (UTC) QuickTime:GPSCoordinates UserData:GPSCoordinates + + Reference: + https://iptc.org/std/photometadata/specification/IPTC-PhotoMetadata-201610_1.pdf """ exif = ( @@ -1447,12 +1482,15 @@ def _exiftool_dict( description = " ".join(rendered) if rendered else "" exif["EXIF:ImageDescription"] = description exif["XMP:Description"] = description + exif["IPTC:Caption-Abstract"] = description elif self.description: exif["EXIF:ImageDescription"] = self.description exif["XMP:Description"] = self.description + exif["IPTC:Caption-Abstract"] = self.description if self.title: exif["XMP:Title"] = self.title + exif["IPTC:ObjectName"] = self.title keyword_list = [] if merge_exif_keywords: @@ -1462,15 +1500,16 @@ def _exiftool_dict( keyword_list.extend(self.keywords) person_list = [] - if merge_exif_persons: - person_list.extend(self._get_exif_persons()) + if persons: + if merge_exif_persons: + person_list.extend(self._get_exif_persons()) - if self.persons: - # filter out _UNKNOWN_PERSON - person_list.extend([p for p in self.persons if p != _UNKNOWN_PERSON]) + if self.persons: + # filter out _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) + if use_persons_as_keywords and person_list: + keyword_list.extend(person_list) if use_albums_as_keywords and self.albums: keyword_list.extend(self.albums) @@ -1514,25 +1553,26 @@ def _exiftool_dict( exif["XMP:Subject"] = keyword_list.copy() exif["XMP:TagsList"] = keyword_list.copy() - if person_list: + if persons and person_list: person_list = sorted(list(set(person_list))) exif["XMP:PersonInImage"] = person_list.copy() # if self.favorite(): # exif["Rating"] = 5 - (lat, lon) = self.location - if lat is not None and lon is not None: - if self.isphoto: - exif["EXIF:GPSLatitude"] = lat - exif["EXIF:GPSLongitude"] = lon - lat_ref = "N" if lat >= 0 else "S" - lon_ref = "E" if lon >= 0 else "W" - exif["EXIF:GPSLatitudeRef"] = lat_ref - exif["EXIF:GPSLongitudeRef"] = lon_ref - elif self.ismovie: - exif["Keys:GPSCoordinates"] = f"{lat} {lon}" - exif["UserData:GPSCoordinates"] = f"{lat} {lon}" + if location: + (lat, lon) = self.location + if lat is not None and lon is not None: + if self.isphoto: + exif["EXIF:GPSLatitude"] = lat + exif["EXIF:GPSLongitude"] = lon + lat_ref = "N" if lat >= 0 else "S" + lon_ref = "E" if lon >= 0 else "W" + exif["EXIF:GPSLatitudeRef"] = lat_ref + exif["EXIF:GPSLongitudeRef"] = lon_ref + elif self.ismovie: + exif["Keys:GPSCoordinates"] = f"{lat} {lon}" + exif["UserData:GPSCoordinates"] = f"{lat} {lon}" # process date/time and timezone offset # Photos exports the following fields and sets modify date to creation date @@ -1640,6 +1680,8 @@ def _exiftool_json_sidecar( merge_exif_keywords=False, merge_exif_persons=False, filename=None, + persons=True, + location=True, ): """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. @@ -1654,13 +1696,17 @@ def _exiftool_json_sidecar( 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) filename: filename of the destination image file for including in exiftool signature in JSON sidecar + persons: if True, include person data + location: if True, include location data Returns: dict with exiftool tags / values Exports the following: EXIF:ImageDescription XMP:Description (may include template) + IPTC:CaptionAbstract XMP:Title + IPTC:ObjectName XMP:TagsList IPTC:Keywords (may include album name, person name, or template) XMP:Subject (set to keywords + person) @@ -1688,6 +1734,8 @@ def _exiftool_json_sidecar( merge_exif_keywords=merge_exif_keywords, merge_exif_persons=merge_exif_persons, filename=filename, + persons=persons, + location=location, ) if not tag_groups: @@ -1710,6 +1758,8 @@ def _xmp_sidecar( extension=None, merge_exif_keywords=False, merge_exif_persons=False, + persons=True, + location=True, ): """returns string for XMP sidecar use_albums_as_keywords: treat album names as keywords @@ -1719,6 +1769,8 @@ def _xmp_sidecar( extension: which extension to use for SidecarForExtension property 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) + persons: if True, include person data + location: if True, include location data """ xmp_template_file = ( @@ -1749,15 +1801,16 @@ 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 persons: + if merge_exif_persons: + person_list.extend(self._get_exif_persons()) - if self.persons: - # filter out _UNKNOWN_PERSON - person_list.extend([p for p in self.persons if p != _UNKNOWN_PERSON]) + if self.persons: + # filter out _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) + if use_persons_as_keywords and person_list: + keyword_list.extend(person_list) if use_albums_as_keywords and self.albums: keyword_list.extend(self.albums) @@ -1787,11 +1840,14 @@ def _xmp_sidecar( # sorted mainly to make testing the XMP file easier if keyword_list: keyword_list = sorted(list(set(keyword_list))) - if person_list: + if persons and person_list: person_list = sorted(list(set(person_list))) subject_list = keyword_list + if location: + latlon = self.location + xmp_str = xmp_template.render( photo=self, description=description, @@ -1799,6 +1855,7 @@ def _xmp_sidecar( persons=person_list, subjects=subject_list, extension=extension, + location=latlon, version=__version__, ) diff --git a/osxphotos/templates/xmp_sidecar.mako b/osxphotos/templates/xmp_sidecar.mako index c3854243..21853a68 100644 --- a/osxphotos/templates/xmp_sidecar.mako +++ b/osxphotos/templates/xmp_sidecar.mako @@ -182,7 +182,7 @@ - ${gps_info(*photo.location)} + ${gps_info(*location)}