From bb4bc8fd96ee1ab3efbd9d175fba2b4a86c15977 Mon Sep 17 00:00:00 2001 From: Rhet Turnbull Date: Sun, 28 Jun 2020 20:10:38 -0700 Subject: [PATCH] Added --description-template to CLI, closes #166 --- osxphotos/__main__.py | 19 +++++++++ osxphotos/_version.py | 2 +- osxphotos/photoinfo/_photoinfo_export.py | 40 +++++++++++++++++-- osxphotos/templates/xmp_sidecar.mako | 2 +- tests/test_cli.py | 50 +++++++++++++++++++++++- 5 files changed, 107 insertions(+), 6 deletions(-) diff --git a/osxphotos/__main__.py b/osxphotos/__main__.py index 0fd4634c..93ec48f0 100644 --- a/osxphotos/__main__.py +++ b/osxphotos/__main__.py @@ -1146,6 +1146,18 @@ def query( '--keyword-template "{created.year}" ' "See Templating System below.", ) +@click.option( + "--description-template", + metavar="TEMPLATE", + multiple=False, + default=None, + help="For use with --exiftool, --sidecar; specify a template string to use as " + "description in the form '{name,DEFAULT}' " + "This is the same format as --directory. For example, if you wanted to append " + "'exported with osxphotos on [today's date]' to the description, you could specify " + '--description-template "{descr} exported with osxphotos on {today.date}" ' + "See Templating System below.", +) @click.option( "--current-name", is_flag=True, @@ -1260,6 +1272,7 @@ def export( person_keyword, album_keyword, keyword_template, + description_template, current_name, sidecar, only_photos, @@ -1505,6 +1518,7 @@ def export( album_keyword=album_keyword, person_keyword=person_keyword, keyword_template=keyword_template, + description_template=description_template, export_db=export_db, fileutil=fileutil, dry_run=dry_run, @@ -1541,6 +1555,7 @@ def export( album_keyword=album_keyword, person_keyword=person_keyword, keyword_template=keyword_template, + description_template=description_template, export_db=export_db, fileutil=fileutil, dry_run=dry_run, @@ -2012,6 +2027,7 @@ def export_photo( album_keyword=None, person_keyword=None, keyword_template=None, + description_template=None, export_db=None, fileutil=FileUtil, dry_run=None, @@ -2039,6 +2055,7 @@ def export_photo( album_keyword: boolean; if True, exports album names as keywords in metadata person_keyword: boolean; if True, exports person names as keywords in metadata keyword_template: list of strings; if provided use rendered template strings as keywords + description_template: string; optional template string that will be rendered for use as photo description export_db: export database instance compatible with ExportDB_ABC fileutil: file util class compatible with FileUtilABC dry_run: boolean; if True, doesn't actually export or update any files @@ -2113,6 +2130,7 @@ def export_photo( use_albums_as_keywords=album_keyword, use_persons_as_keywords=person_keyword, keyword_template=keyword_template, + description_template=description_template, update=update, export_db=export_db, fileutil=fileutil, @@ -2168,6 +2186,7 @@ def export_photo( use_albums_as_keywords=album_keyword, use_persons_as_keywords=person_keyword, keyword_template=keyword_template, + description_template=description_template, update=update, export_db=export_db, fileutil=fileutil, diff --git a/osxphotos/_version.py b/osxphotos/_version.py index ef3a4445..d5a09a87 100644 --- a/osxphotos/_version.py +++ b/osxphotos/_version.py @@ -1,3 +1,3 @@ """ version info """ -__version__ = "0.30.2" +__version__ = "0.30.3" diff --git a/osxphotos/photoinfo/_photoinfo_export.py b/osxphotos/photoinfo/_photoinfo_export.py index e2dd69e8..b4ee0c0b 100644 --- a/osxphotos/photoinfo/_photoinfo_export.py +++ b/osxphotos/photoinfo/_photoinfo_export.py @@ -215,6 +215,7 @@ def export( use_albums_as_keywords=False, use_persons_as_keywords=False, keyword_template=None, + description_template=None, ): """ export photo dest: must be valid destination path (or exception raised) @@ -250,6 +251,7 @@ def export( use_persons_as_keywords: (boolean, default = False); if True, will include person names in keywords when exporting metadata with exiftool or sidecar keyword_template: (list of strings); list of template strings that will be rendered as used as keywords + description_template: string; optional template string that will be rendered for use as photo description returns: list of photos exported """ @@ -273,6 +275,7 @@ def export( use_albums_as_keywords=use_albums_as_keywords, use_persons_as_keywords=use_persons_as_keywords, keyword_template=keyword_template, + description_template=description_template, ) return results.exported @@ -297,6 +300,7 @@ def export2( use_albums_as_keywords=False, use_persons_as_keywords=False, keyword_template=None, + description_template=None, update=False, export_db=None, fileutil=FileUtil, @@ -336,6 +340,7 @@ def export2( use_persons_as_keywords: (boolean, default = False); if True, will include person names in keywords when exporting metadata with exiftool or sidecar keyword_template: (list of strings); list of template strings that will be rendered as used as keywords + description_template: string; optional template string that will be rendered for use as photo description update: (boolean, default=False); if True export will run in update mode, that is, it will not export the photo if the current version already exists in the destination export_db: (ExportDB_ABC); instance of a class that conforms to ExportDB_ABC with methods @@ -670,6 +675,7 @@ def export2( use_albums_as_keywords=use_albums_as_keywords, use_persons_as_keywords=use_persons_as_keywords, keyword_template=keyword_template, + description_template=description_template, ) if not dry_run: try: @@ -685,6 +691,7 @@ def export2( use_albums_as_keywords=use_albums_as_keywords, use_persons_as_keywords=use_persons_as_keywords, keyword_template=keyword_template, + description_template=description_template, ) if not dry_run: try: @@ -712,6 +719,7 @@ def export2( use_albums_as_keywords=use_albums_as_keywords, use_persons_as_keywords=use_persons_as_keywords, keyword_template=keyword_template, + description_template=description_template, ) )[0] if old_data != current_data: @@ -727,6 +735,7 @@ def export2( use_albums_as_keywords=use_albums_as_keywords, use_persons_as_keywords=use_persons_as_keywords, keyword_template=keyword_template, + description_template=description_template, ) export_db.set_exifdata_for_file( exported_file, @@ -734,6 +743,7 @@ def export2( use_albums_as_keywords=use_albums_as_keywords, use_persons_as_keywords=use_persons_as_keywords, keyword_template=keyword_template, + description_template=description_template, ), ) export_db.set_stat_exif_for_file( @@ -749,6 +759,7 @@ def export2( use_albums_as_keywords=use_albums_as_keywords, use_persons_as_keywords=use_persons_as_keywords, keyword_template=keyword_template, + description_template=description_template, ) export_db.set_exifdata_for_file( exported_file, @@ -756,6 +767,7 @@ def export2( use_albums_as_keywords=use_albums_as_keywords, use_persons_as_keywords=use_persons_as_keywords, keyword_template=keyword_template, + description_template=description_template, ), ) export_db.set_stat_exif_for_file( @@ -955,6 +967,7 @@ def _write_exif_data( use_albums_as_keywords=False, use_persons_as_keywords=False, keyword_template=None, + description_template=None, ): """ write exif data to image file at filepath filepath: full path to the image file """ @@ -966,6 +979,7 @@ def _write_exif_data( use_albums_as_keywords=use_albums_as_keywords, use_persons_as_keywords=use_persons_as_keywords, keyword_template=keyword_template, + description_template=description_template, ) )[0] for exiftag, val in exif_info.items(): @@ -984,6 +998,7 @@ def _exiftool_json_sidecar( use_albums_as_keywords=False, use_persons_as_keywords=False, keyword_template=None, + description_template=None, ): """ return json string of EXIF details in exiftool sidecar format Does not include all the EXIF fields as those are likely already in the image @@ -1009,7 +1024,13 @@ def _exiftool_json_sidecar( exif = {} exif["_CreatedBy"] = "osxphotos, https://github.com/RhetTbull/osxphotos" - if self.description: + if description_template is not None: + description = self.render_template( + description_template, expand_inplace=True, inplace_sep=", " + )[0] + exif["EXIF:ImageDescription"] = description + exif["XMP:Description"] = description + elif self.description: exif["EXIF:ImageDescription"] = self.description exif["XMP:Description"] = self.description @@ -1112,16 +1133,25 @@ def _xmp_sidecar( use_albums_as_keywords=False, use_persons_as_keywords=False, keyword_template=None, + description_template=None, ): """ 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 """ + 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 """ # TODO: add additional fields to XMP file? xmp_template = Template(filename=os.path.join(_TEMPLATE_DIR, _XMP_TEMPLATE_NAME)) + if description_template is not None: + description = self.render_template( + description_template, expand_inplace=True, inplace_sep=", " + )[0] + else: + description = self.description if self.description is not None else "" + keyword_list = [] if self.keywords: keyword_list.extend(self.keywords) @@ -1178,7 +1208,11 @@ def _xmp_sidecar( subject_list = list(self.keywords) + person_list xmp_str = xmp_template.render( - photo=self, keywords=keyword_list, persons=person_list, subjects=subject_list + photo=self, + description=description, + keywords=keyword_list, + persons=person_list, + subjects=subject_list, ) # remove extra lines that mako inserts from template diff --git a/osxphotos/templates/xmp_sidecar.mako b/osxphotos/templates/xmp_sidecar.mako index 792cc831..3b9badb2 100644 --- a/osxphotos/templates/xmp_sidecar.mako +++ b/osxphotos/templates/xmp_sidecar.mako @@ -77,7 +77,7 @@ - ${dc_description(photo.description)} + ${dc_description(description)} ${dc_title(photo.title)} ${dc_subject(subjects)} ${dc_datecreated(photo.date)} diff --git a/tests/test_cli.py b/tests/test_cli.py index e2f05812..099a8677 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -197,7 +197,13 @@ CLI_EXPORT_RAW_EDITED = [ ] CLI_EXPORT_RAW_EDITED_ORIGINAL = ["IMG_0476_2.CR2", "IMG_0476_2_edited.jpeg"] -CLI_UUID_DICT_15_5 = {"intrash": "71E3E212-00EB-430D-8A63-5E294B268554"} +CLI_UUID_DICT_15_5 = { + "intrash": "71E3E212-00EB-430D-8A63-5E294B268554", + "template": "F12384F6-CD17-4151-ACBA-AE0E3688539E", +} + +CLI_TEMPLATE_SIDECAR_FILENAME = "Pumkins1.json" + CLI_UUID_DICT_14_6 = {"intrash": "3tljdX43R8+k6peNHVrJNQ"} PHOTOS_NOT_IN_TRASH_LEN_14_6 = 7 @@ -1095,6 +1101,48 @@ def test_export_sidecar(): assert sorted(files) == sorted(CLI_EXPORT_SIDECAR_FILENAMES) +def test_export_sidecar_templates(): + import json + 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, PHOTOS_DB_15_5), + ".", + "--sidecar=json", + f"--uuid={CLI_UUID_DICT_15_5['template']}", + "-V", + "--keyword-template", + "{person}", + "--description-template", + "{descr} {person} {keyword} {album}", + ], + ) + assert result.exit_code == 0 + assert os.path.isfile(CLI_TEMPLATE_SIDECAR_FILENAME) + with open(CLI_TEMPLATE_SIDECAR_FILENAME, "r") as jsonfile: + exifdata = json.load(jsonfile) + assert ( + exifdata[0]["XMP:Description"][0] + == "Girls with pumpkins Katie, Suzy Kids Pumpkin Farm, Test Album" + ) + assert ( + exifdata[0]["EXIF:ImageDescription"][0] + == "Girls with pumpkins Katie, Suzy Kids Pumpkin Farm, Test Album" + ) + + def test_export_live(): import glob import os