Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1cb99f83f | ||
|
|
d3605f6303 | ||
|
|
dce002cdfe | ||
|
|
7bd189e9b2 | ||
|
|
baa86c77f6 |
21
CHANGELOG.md
21
CHANGELOG.md
@@ -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
|
||||
|
||||
29
README.md
29
README.md
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.38.16"
|
||||
__version__ = "0.38.18"
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 |
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user