diff --git a/README.md b/README.md index 10bec369..d06c12b1 100644 --- a/README.md +++ b/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. diff --git a/osxphotos/__main__.py b/osxphotos/__main__.py index 97083267..54337b64 100644 --- a/osxphotos/__main__.py +++ b/osxphotos/__main__.py @@ -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; " @@ -1571,6 +1581,7 @@ def export( convert_to_jpeg, jpeg_quality, sidecar, + sidecar_drop_ext, only_photos, only_movies, burst, @@ -2036,6 +2047,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, @@ -2084,6 +2096,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, @@ -2616,6 +2629,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, @@ -2654,6 +2668,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 @@ -2804,6 +2819,7 @@ 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, @@ -2908,6 +2924,7 @@ 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, diff --git a/osxphotos/_version.py b/osxphotos/_version.py index de9c1ed7..41aca264 100644 --- a/osxphotos/_version.py +++ b/osxphotos/_version.py @@ -1,5 +1,5 @@ """ version info """ -__version__ = "0.38.16" +__version__ = "0.38.17" diff --git a/osxphotos/photoinfo/_photoinfo_export.py b/osxphotos/photoinfo/_photoinfo_export.py index 48a89f63..7d998720 100644 --- a/osxphotos/photoinfo/_photoinfo_export.py +++ b/osxphotos/photoinfo/_photoinfo_export.py @@ -430,6 +430,7 @@ def export2( overwrite=False, increment=True, sidecar=0, + sidecar_drop_ext=False, use_photos_export=False, timeout=120, exiftool=False, @@ -475,6 +476,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 @@ -893,8 +895,9 @@ 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, @@ -913,7 +916,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, @@ -933,7 +936,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, diff --git a/tests/test_cli.py b/tests/test_cli.py index 24d33679..292488a2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -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", @@ -1974,6 +1979,7 @@ def test_query_deleted_4(): def test_export_sidecar(): + """ test --sidecar """ import glob import os import os.path @@ -2003,6 +2009,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