Implemented --favorite-rating, #732
This commit is contained in:
parent
b4caea15fa
commit
5d33dcdcc3
@ -364,6 +364,13 @@ from .verbose import get_verbose_console, time_stamp, verbose_print
|
||||
is_flag=True,
|
||||
help="Merge any persons found in the original file with persons used for '--exiftool' and '--sidecar'.",
|
||||
)
|
||||
@click.option(
|
||||
"--favorite-rating",
|
||||
is_flag=True,
|
||||
help="When used with --exiftool or --sidecar, "
|
||||
"set XMP:Rating=5 for photos marked as Favorite and XMP:Rating=0 for non-Favorites. "
|
||||
"If not specified, XMP:Rating is not set.",
|
||||
)
|
||||
@click.option(
|
||||
"--ignore-date-modified",
|
||||
is_flag=True,
|
||||
@ -730,6 +737,7 @@ def export(
|
||||
exportdb,
|
||||
external_edit,
|
||||
favorite,
|
||||
favorite_rating,
|
||||
filename_template,
|
||||
finder_tag_keywords,
|
||||
finder_tag_template,
|
||||
@ -949,6 +957,7 @@ def export(
|
||||
exportdb = cfg.exportdb
|
||||
external_edit = cfg.external_edit
|
||||
favorite = cfg.favorite
|
||||
favorite_rating = cfg.favorite_rating
|
||||
filename_template = cfg.filename_template
|
||||
finder_tag_keywords = cfg.finder_tag_keywords
|
||||
finder_tag_template = cfg.finder_tag_template
|
||||
@ -1102,6 +1111,7 @@ def export(
|
||||
dependent_options = [
|
||||
("exiftool_merge_keywords", ("exiftool", "sidecar")),
|
||||
("exiftool_merge_persons", ("exiftool", "sidecar")),
|
||||
("favorite_rating", ("exiftool", "sidecar")),
|
||||
("exiftool_option", ("exiftool")),
|
||||
("ignore_signature", ("update", "force_update")),
|
||||
("jpeg_quality", ("convert_to_jpeg")),
|
||||
@ -1470,6 +1480,7 @@ def export(
|
||||
export_live=export_live,
|
||||
export_preview=preview,
|
||||
export_raw=export_raw,
|
||||
favorite_rating=favorite_rating,
|
||||
filename_template=filename_template,
|
||||
fileutil=fileutil,
|
||||
force_update=force_update,
|
||||
@ -1725,6 +1736,7 @@ def export_photo(
|
||||
exiftool_merge_keywords=False,
|
||||
exiftool_merge_persons=False,
|
||||
directory=None,
|
||||
favorite_rating=False,
|
||||
filename_template=None,
|
||||
export_raw=None,
|
||||
album_keyword=None,
|
||||
@ -1778,6 +1790,7 @@ def export_photo(
|
||||
export_live: bool; also export live video component if photo is a live photo; live video will have same name as photo but with .mov extension
|
||||
export_preview: export the preview image generated by Photos
|
||||
export_raw: bool; if True exports raw image associate with the photo
|
||||
favorite_rating: bool; if True, set XMP:Rating=5 for favorite images and XMP:Rating=0 for non-favorites
|
||||
filename_template: template use to determine output file
|
||||
fileutil: file util class compatible with FileUtilABC
|
||||
force_update: bool, only export updated photos but trigger export even if only metadata has changed
|
||||
@ -1943,6 +1956,7 @@ def export_photo(
|
||||
export_original=export_original,
|
||||
export_preview=export_preview,
|
||||
export_raw=export_raw,
|
||||
favorite_rating=favorite_rating,
|
||||
filename=original_filename,
|
||||
fileutil=fileutil,
|
||||
force_update=force_update,
|
||||
@ -2057,6 +2071,7 @@ def export_photo(
|
||||
export_original=False,
|
||||
export_preview=not export_original and export_preview,
|
||||
export_raw=not export_original and export_raw,
|
||||
favorite_rating=favorite_rating,
|
||||
filename=edited_filename,
|
||||
fileutil=fileutil,
|
||||
force_update=force_update,
|
||||
@ -2142,6 +2157,7 @@ def export_photo_to_directory(
|
||||
export_original,
|
||||
export_preview,
|
||||
export_raw,
|
||||
favorite_rating,
|
||||
filename,
|
||||
fileutil,
|
||||
force_update,
|
||||
@ -2203,6 +2219,7 @@ def export_photo_to_directory(
|
||||
exiftool=exiftool,
|
||||
export_as_hardlink=export_as_hardlink,
|
||||
export_db=export_db,
|
||||
favorite_rating=favorite_rating,
|
||||
fileutil=fileutil,
|
||||
force_update=force_update,
|
||||
ignore_date_modified=ignore_date_modified,
|
||||
|
||||
@ -136,6 +136,7 @@ class ExportOptions:
|
||||
use_photokit (bool, default=False): if True, will use photokit to export photos when use_photos_export is True
|
||||
verbose (callable): optional callable function to use for printing verbose text during processing; if None (default), does not print output.
|
||||
tmpdir: (str, default=None): Optional directory to use for temporary files, if None (default) uses system tmp directory
|
||||
favorite_rating (bool): if True, set XMP:Rating=5 for favorite images and XMP:Rating=0 for non-favorites
|
||||
|
||||
"""
|
||||
|
||||
@ -181,6 +182,7 @@ class ExportOptions:
|
||||
use_photos_export: bool = False
|
||||
verbose: t.Optional[t.Callable] = None
|
||||
tmpdir: t.Optional[str] = None
|
||||
favorite_rating: bool = False
|
||||
|
||||
def asdict(self):
|
||||
return asdict(self)
|
||||
@ -1586,6 +1588,7 @@ class PhotoExporter:
|
||||
QuickTime:ModifyDate (UTC)
|
||||
QuickTime:GPSCoordinates
|
||||
UserData:GPSCoordinates
|
||||
XMP:Rating
|
||||
|
||||
Reference:
|
||||
https://iptc.org/std/photometadata/specification/IPTC-PhotoMetadata-201610_1.pdf
|
||||
@ -1701,8 +1704,8 @@ class PhotoExporter:
|
||||
if options.face_regions and self.photo.face_info:
|
||||
exif.update(self._get_mwg_face_regions_exiftool())
|
||||
|
||||
# if self.favorite():
|
||||
# exif["Rating"] = 5
|
||||
if options.favorite_rating:
|
||||
exif["XMP:Rating"] = 5 if self.photo.favorite else 0
|
||||
|
||||
if options.location:
|
||||
(lat, lon) = self.photo.location
|
||||
@ -2009,6 +2012,11 @@ class PhotoExporter:
|
||||
|
||||
latlon = self.photo.location if options.location else (None, None)
|
||||
|
||||
if options.favorite_rating:
|
||||
rating = 5 if self.photo.favorite else 0
|
||||
else:
|
||||
rating = None
|
||||
|
||||
xmp_str = xmp_template.render(
|
||||
photo=self.photo,
|
||||
description=description,
|
||||
@ -2018,6 +2026,7 @@ class PhotoExporter:
|
||||
extension=extension,
|
||||
location=latlon,
|
||||
version=__version__,
|
||||
rating=rating,
|
||||
)
|
||||
|
||||
# remove extra lines that mako inserts from template
|
||||
|
||||
@ -92,6 +92,12 @@
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="xmp_rating(rating)">
|
||||
% if rating is not None:
|
||||
<xmp:Rating>${rating}</xmp:Rating>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="gps_info(latitude, longitude)">
|
||||
% if latitude is not None and longitude is not None:
|
||||
<exif:GPSLongitude>${int(abs(longitude))},${(abs(longitude) % 1) * 60}${"E" if longitude >= 0 else "W"}</exif:GPSLongitude>
|
||||
@ -174,6 +180,7 @@
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
${adobe_createdate(photo.date)}
|
||||
${adobe_modifydate(photo.date)}
|
||||
${xmp_rating(rating)}
|
||||
</rdf:Description>
|
||||
|
||||
<rdf:Description rdf:about=""
|
||||
|
||||
@ -92,6 +92,12 @@
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="xmp_rating(rating)">
|
||||
% if rating is not None:
|
||||
<xmp:Rating>${rating}</xmp:Rating>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="gps_info(latitude, longitude)">
|
||||
% if latitude is not None and longitude is not None:
|
||||
<exif:GPSLongitude>${int(abs(longitude))},${(abs(longitude) % 1) * 60}${"E" if longitude >= 0 else "W"}</exif:GPSLongitude>
|
||||
@ -174,6 +180,7 @@
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
${adobe_createdate(photo.date)}
|
||||
${adobe_modifydate(photo.date)}
|
||||
${xmp_rating(rating)}
|
||||
</rdf:Description>
|
||||
|
||||
<rdf:Description rdf:about=""
|
||||
|
||||
@ -9,6 +9,7 @@ import os.path
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sqlite3
|
||||
import tempfile
|
||||
import time
|
||||
@ -987,6 +988,11 @@ CLI_EXPORT_LIVE_EDITED = [
|
||||
"IMG_4813_edited.mov",
|
||||
]
|
||||
|
||||
UUID_FAVORITE = "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51"
|
||||
FILE_FAVORITE = "wedding.jpg"
|
||||
UUID_NOT_FAVORITE = "1EB2B765-0765-43BA-A90C-0D0580E6172C"
|
||||
FILE_NOT_FAVORITE = "Pumpkins3.jpg"
|
||||
|
||||
|
||||
def modify_file(filename):
|
||||
"""appends data to a file to modify it"""
|
||||
@ -2264,6 +2270,34 @@ def test_export_exiftool_merge_sidecar():
|
||||
assert exif[key] == CLI_EXIFTOOL_MERGE[uuid][key]
|
||||
|
||||
|
||||
@pytest.mark.skipif(exiftool is None, reason="exiftool not installed")
|
||||
def test_export_exiftool_favorite_rating():
|
||||
"""Test --exiftol --favorite-rating"""
|
||||
|
||||
runner = CliRunner()
|
||||
cwd = os.getcwd()
|
||||
# pylint: disable=not-context-manager
|
||||
with runner.isolated_filesystem():
|
||||
for uuid in CLI_EXIFTOOL:
|
||||
result = runner.invoke(
|
||||
export,
|
||||
[
|
||||
os.path.join(cwd, PHOTOS_DB_15_7),
|
||||
".",
|
||||
"-V",
|
||||
"--exiftool",
|
||||
"--uuid",
|
||||
UUID_FAVORITE,
|
||||
"--uuid",
|
||||
UUID_NOT_FAVORITE,
|
||||
"--favorite-rating",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert ExifTool(FILE_FAVORITE).asdict()["XMP:Rating"] == 5
|
||||
assert ExifTool(FILE_NOT_FAVORITE).asdict()["XMP:Rating"] == 0
|
||||
|
||||
|
||||
def test_export_edited_suffix():
|
||||
"""test export with --edited-suffix"""
|
||||
|
||||
@ -3067,6 +3101,49 @@ def test_export_sidecar():
|
||||
assert sorted(files) == sorted(CLI_EXPORT_SIDECAR_FILENAMES)
|
||||
|
||||
|
||||
def test_export_sidecar_favorite_rating():
|
||||
"""test --sidecar --favorite-rating"""
|
||||
|
||||
runner = CliRunner()
|
||||
cwd = os.getcwd()
|
||||
# pylint: disable=not-context-manager
|
||||
with runner.isolated_filesystem():
|
||||
result = runner.invoke(
|
||||
cli_main,
|
||||
[
|
||||
"export",
|
||||
"--db",
|
||||
os.path.join(cwd, CLI_PHOTOS_DB),
|
||||
".",
|
||||
"--sidecar=json",
|
||||
"--sidecar=xmp",
|
||||
f"--uuid={UUID_FAVORITE}",
|
||||
f"--uuid={UUID_NOT_FAVORITE}",
|
||||
"--favorite-rating",
|
||||
"-V",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
with open(f"{FILE_FAVORITE}.json") as fp:
|
||||
json_sidecar = json.load(fp)
|
||||
assert json_sidecar[0]["XMP:Rating"] == 5
|
||||
with open(f"{FILE_NOT_FAVORITE}.json") as fp:
|
||||
json_sidecar = json.load(fp)
|
||||
assert json_sidecar[0]["XMP:Rating"] == 0
|
||||
|
||||
results = subprocess.run(
|
||||
["grep", "xmp:Rating", f"{FILE_FAVORITE}.xmp"], capture_output=True
|
||||
)
|
||||
results_stdout = results.stdout.decode("utf-8")
|
||||
assert "<xmp:Rating>5</xmp:Rating>" in results_stdout
|
||||
|
||||
results = subprocess.run(
|
||||
["grep", "xmp:Rating", f"{FILE_NOT_FAVORITE}.xmp"], capture_output=True
|
||||
)
|
||||
results_stdout = results.stdout.decode("utf-8")
|
||||
assert "<xmp:Rating>0</xmp:Rating>" in results_stdout
|
||||
|
||||
|
||||
def test_export_sidecar_drop_ext():
|
||||
"""test --sidecar with --sidecar-drop-ext option"""
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user