diff --git a/osxphotos/__main__.py b/osxphotos/__main__.py index ca32fc13..51e4c414 100644 --- a/osxphotos/__main__.py +++ b/osxphotos/__main__.py @@ -629,10 +629,10 @@ def query( help="Create sidecar for each photo exported; valid FORMAT values: xmp, json; " f"--sidecar json: create JSON sidecar useable by exiftool ({_EXIF_TOOL_URL}) " "The sidecar file can be used to apply metadata to the file with exiftool, for example: " - '"exiftool -j=photoname.jpg.json photoname.jpg" ' - "The sidecar file is named in format photoname.ext.json where ext is extension of the photo (e.g. jpg). " + '"exiftool -j=photoname.json photoname.jpg" ' + "The sidecar file is named in format photoname.json " "--sidecar xmp: create XMP sidecar used by Adobe Lightroom, etc." - "The sidecar file is named in format photoname.ext.xmp where ext is extension of the photo (e.g. jpg). " + "The sidecar file is named in format photoname.xmp" ) @click.option( "--download-missing", diff --git a/osxphotos/_version.py b/osxphotos/_version.py index f229a01e..e0f4f523 100644 --- a/osxphotos/_version.py +++ b/osxphotos/_version.py @@ -1,3 +1,3 @@ """ version info """ -__version__ = "0.22.6" +__version__ = "0.22.7" diff --git a/osxphotos/photoinfo.py b/osxphotos/photoinfo.py index 4303fd83..18a168f2 100644 --- a/osxphotos/photoinfo.py +++ b/osxphotos/photoinfo.py @@ -4,6 +4,7 @@ Represents a single photo in the Photos library and provides access to the photo PhotosDB.photos() returns a list of PhotoInfo objects """ +import glob import json import logging import os.path @@ -32,8 +33,6 @@ from .utils import ( dd_to_dms_str, ) -# TODO: check pylint output - class PhotoInfo: """ @@ -525,13 +524,20 @@ class PhotoInfo: dest = dest / filename # check to see if file exists and if so, add (1), (2), etc until we find one that works + # Photos checks the stem and adds (1), (2), etc which avoids collision with sidecars + # e.g. exporting sidecar for file1.png and file1.jpeg + # if file1.png exists and exporting file1.jpeg, + # dest will be file1 (1).jpeg even though file1.jpeg doesn't exist to prevent sidecar collision if increment and not overwrite: count = 1 - dest_new = dest - while dest_new.exists(): - dest_new = dest.parent / f"{dest.stem} ({count}){dest.suffix}" + glob_str = str(dest.parent / f"{dest.stem}*") + dest_files = glob.glob(glob_str) + dest_files = [pathlib.Path(f).stem for f in dest_files] + dest_new = dest.stem + while dest_new in dest_files: + dest_new = f"{dest.stem} ({count})" count += 1 - dest = dest_new + dest = dest.parent / f"{dest_new}{dest.suffix}" # if overwrite==False and #increment==False, export should fail if file exists if dest.exists() and not overwrite and not increment: @@ -597,7 +603,7 @@ class PhotoInfo: if sidecar_json: logging.debug("writing exiftool_json_sidecar") - sidecar_filename = f"{dest}.json" + sidecar_filename = dest.parent / pathlib.Path(f"{dest.stem}.json") sidecar_str = self._exiftool_json_sidecar() try: self._write_sidecar(sidecar_filename, sidecar_str) @@ -607,7 +613,7 @@ class PhotoInfo: if sidecar_xmp: logging.debug("writing xmp_sidecar") - sidecar_filename = f"{dest}.xmp" + sidecar_filename = dest.parent / pathlib.Path(f"{dest.stem}.xmp") sidecar_str = self._xmp_sidecar() try: self._write_sidecar(sidecar_filename, sidecar_str) @@ -637,6 +643,7 @@ class PhotoInfo: ModifyDate """ exif = {} + exif["_CreatedBy"] = "osxphotos, https://github.com/RhetTbull/osxphotos" exif["FileName"] = self.filename if self.description: @@ -693,10 +700,14 @@ class PhotoInfo: def _xmp_sidecar(self): """ returns string for XMP sidecar """ + # TODO: add additional fields to XMP file? + xmp_template = Template( filename=os.path.join(_TEMPLATE_DIR, _XMP_TEMPLATE_NAME) ) xmp_str = xmp_template.render(photo=self) + # remove extra lines that mako inserts from template + xmp_str = "\n".join([line for line in xmp_str.split("\n") if line.strip() != ""]) return xmp_str def _write_sidecar(self, filename, sidecar_str): diff --git a/tests/test_cli.py b/tests/test_cli.py index 7cf57c3c..1979c1a2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -35,6 +35,10 @@ CLI_EXPORT_FILENAMES = [ "wedding_edited.jpg", ] +CLI_EXPORT_UUID = "D79B8D77-BFFC-460B-9312-034F2877D35B" + +CLI_EXPORT_SIDECAR_FILENAMES = ["Pumkins2.jpg", "Pumpkins2.json", "Pumpkins2.xmp"] + def test_osxphotos(): import osxphotos @@ -118,10 +122,36 @@ def test_query_date(): "--db", "./tests/Test-10.15.1.photoslibrary", "--from-date=2018-09-28", - "--to-date=2018-09-28T23:00:00" + "--to-date=2018-09-28T23:00:00", ], ) assert result.exit_code == 0 json_got = json.loads(result.output) - assert len(json_got) == 4 \ No newline at end of file + assert len(json_got) == 4 + + +def test_export_sidecar(): + import glob + import os + import os.path + import osxphotos + from osxphotos.__main__ import export + + runner = CliRunner() + cwd = os.getcwd() + with runner.isolated_filesystem(): + result = runner.invoke( + export, + [ + os.path.join(cwd, "tests/Test-10.15.1.photoslibrary"), + ".", + "--original-name", + "-V", + "--sidecar json", + "--sidecar xmp", + f"--uuid {CLI_EXPORT_UUID}", + ], + ) + files = glob.glob("*") + assert files.sort() == CLI_EXPORT_SIDECAR_FILENAMES.sort() diff --git a/tests/test_export_catalina_10_15_1.py b/tests/test_export_catalina_10_15_1.py index 5ab9ed13..f15b102d 100644 --- a/tests/test_export_catalina_10_15_1.py +++ b/tests/test_export_catalina_10_15_1.py @@ -56,6 +56,7 @@ UUID_DICT = { "external_edit": "DC99FBDD-7A52-4100-A5BB-344131646C30", "no_external_edit": "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51", "export": "D79B8D77-BFFC-460B-9312-034F2877D35B", # "Pumkins2.jpg" + "xmp": "F12384F6-CD17-4151-ACBA-AE0E3688539E", } @@ -471,3 +472,62 @@ def test_exiftool_json_sidecar(): else: assert item[0][1] == item[1][1] + +def test_xmp_sidecar(): + import osxphotos + + photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) + photos = photosdb.photos(uuid=[UUID_DICT["xmp"]]) + + xmp_expected = """ + + + + + Girls with pumpkins + Can we carry this? + + + + Kids + Suzy + Katie + + + 2018-09-28T15:35:49.063000-04:00 + + + + + Suzy + Katie + + + + + + + Kids + + + + + 2018-09-28T15:35:49 + 2018-09-28T15:35:49 + + + """ + + xmp_expected_lines = [line.strip() for line in xmp_expected.split("\n")] + + xmp_got = photos[0]._xmp_sidecar() + xmp_got_lines = [line.strip() for line in xmp_got.split("\n")] + + for line_expected, line_got in zip(xmp_expected_lines, xmp_got_lines): + assert line_expected == line_got + diff --git a/tests/test_export_mojave_10_14_6.py b/tests/test_export_mojave_10_14_6.py index 40ffc616..69621050 100644 --- a/tests/test_export_mojave_10_14_6.py +++ b/tests/test_export_mojave_10_14_6.py @@ -42,6 +42,7 @@ UUID_DICT = { "no_adjustments": "15uNd7%8RguTEgNPKHfTWw", "export": "15uNd7%8RguTEgNPKHfTWw", "location": "3Jn73XpSQQCluzRBMWRsMA", + "xmp": "8SOE9s0XQVGsuq4ONohTng", } @@ -413,3 +414,63 @@ def test_exiftool_json_sidecar(): assert sorted(item[0][1]) == sorted(item[1][1]) else: assert item[0][1] == item[1][1] + + +def test_xmp_sidecar(): + import osxphotos + + photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) + photos = photosdb.photos(uuid=[UUID_DICT["xmp"]]) + + xmp_expected = """ + + + + + Girls with pumpkins + Can we carry this? + + + + Kids + Suzy + Katie + + + 2018-09-28T15:35:49.063000-04:00 + + + + + Suzy + Katie + + + + + + + Kids + + + + + 2018-09-28T15:35:49 + 2018-09-28T15:35:49 + + + """ + + xmp_expected_lines = [line.strip() for line in xmp_expected.split("\n")] + + xmp_got = photos[0]._xmp_sidecar() + xmp_got_lines = [line.strip() for line in xmp_got.split("\n")] + + for line_expected, line_got in zip(xmp_expected_lines, xmp_got_lines): + assert line_expected == line_got +