Partial fix for #155

This commit is contained in:
Rhet Turnbull
2020-06-06 12:09:51 -07:00
parent 5c7a0c3a24
commit 62d096b5a1
35 changed files with 107 additions and 42 deletions

View File

@@ -156,6 +156,46 @@ def _export_photo_uuid_applescript(
return None return None
# _check_export_suffix is not a class method, don't import this into PhotoInfo
def _check_export_suffix(src, dest, edited):
"""Helper function for exporting photos to check file extensions of destination path.
Checks that dst file extension is appropriate for the src.
If edited=True, will use src file extension of ".jpeg" if None provided for src.
Args:
src: path to source file or None.
dest: path to destination file.
edited: set to True if exporting an edited photo.
Returns:
True if src and dest extensions are OK, else False.
Raises:
ValueError if edited is False and src is None
"""
# check extension of destination
if src is not None:
# use suffix from edited file
actual_suffix = pathlib.Path(src).suffix
elif edited:
# use .jpeg as that's probably correct
actual_suffix = ".jpeg"
else:
raise ValueError("src must not be None if edited=False")
# Photo's often converts .JPG to .jpeg or .tif to .tiff on import
dest_ext = dest.suffix.lower()
actual_ext = actual_suffix.lower()
suffixes = sorted([dest_ext, actual_ext])
return (
dest_ext == actual_ext
or suffixes == [".jpeg", ".jpg"]
or suffixes == [".tif", ".tiff"]
)
def export( def export(
self, self,
dest, dest,
@@ -182,8 +222,11 @@ def export(
**NOTE**: if provided, user must ensure file extension (suffix) is correct. **NOTE**: if provided, user must ensure file extension (suffix) is correct.
For example, if photo is .CR2 file, edited image may be .jpeg. For example, if photo is .CR2 file, edited image may be .jpeg.
If you provide an extension different than what the actual file is, If you provide an extension different than what the actual file is,
export will print a warning but will happily export the photo using the export will print a warning but will export the photo using the
incorrect file extension. e.g. to get the extension of the edited photo, incorrect file extension (unless use_photos_export is true, in which case export will
use the extension provided by Photos upon export; in this case, an incorrect extension is
silently ignored).
e.g. to get the extension of the edited photo,
reference PhotoInfo.path_edited reference PhotoInfo.path_edited
edited: (boolean, default=False); if True will export the edited version of the photo edited: (boolean, default=False); if True will export the edited version of the photo
(or raise exception if no edited version) (or raise exception if no edited version)
@@ -260,13 +303,16 @@ def export2(
dry_run=False, dry_run=False,
): ):
""" export photo, like export but with update and dry_run options """ export photo, like export but with update and dry_run options
dest: must be valid destination path (or exception raised) dest: must be valid destination path or exception raised
filename: (optional): name of exported picture; if not provided, will use current filename filename: (optional): name of exported picture; if not provided, will use current filename
**NOTE**: if provided, user must ensure file extension (suffix) is correct. **NOTE**: if provided, user must ensure file extension (suffix) is correct.
For example, if photo is .CR2 file, edited image may be .jpeg. For example, if photo is .CR2 file, edited image may be .jpeg.
If you provide an extension different than what the actual file is, If you provide an extension different than what the actual file is,
export will print a warning but will happily export the photo using the export will print a warning but will export the photo using the
incorrect file extension. e.g. to get the extension of the edited photo, incorrect file extension (unless use_photos_export is true, in which case export will
use the extension provided by Photos upon export; in this case, an incorrect extension is
silently ignored).
e.g. to get the extension of the edited photo,
reference PhotoInfo.path_edited reference PhotoInfo.path_edited
edited: (boolean, default=False); if True will export the edited version of the photo edited: (boolean, default=False); if True will export the edited version of the photo
(or raise exception if no edited version) (or raise exception if no edited version)
@@ -370,27 +416,6 @@ def export2(
fname = pathlib.Path(fname) fname = pathlib.Path(fname)
dest = dest / fname dest = dest / fname
# check extension of destination
if edited and self.path_edited is not None:
# use suffix from edited file
actual_suffix = pathlib.Path(self.path_edited).suffix
elif edited:
# use .jpeg as that's probably correct
# if edited and path_edited is None, will raise FileNotFoundError below
# unless use_photos_export is True
actual_suffix = ".jpeg"
else:
# use suffix from the non-edited file
actual_suffix = pathlib.Path(self.filename).suffix
# warn if suffixes don't match but ignore .JPG / .jpeg as
# Photo's often converts .JPG to .jpeg
suffixes = sorted([x.lower() for x in [dest.suffix, actual_suffix]])
if dest.suffix.lower() != actual_suffix.lower() and suffixes != [".jpeg", ".jpg"]:
logging.warning(
f"Invalid destination suffix: {dest.suffix}, should be {actual_suffix}"
)
# check to see if file exists and if so, add (1), (2), etc until we find one that works # 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 # Photos checks the stem and adds (1), (2), etc which avoids collision with sidecars
# e.g. exporting sidecar for file1.png and file1.jpeg # e.g. exporting sidecar for file1.png and file1.jpeg
@@ -438,6 +463,13 @@ def export2(
if not os.path.isfile(src): if not os.path.isfile(src):
raise FileNotFoundError(f"{src} does not appear to exist") raise FileNotFoundError(f"{src} does not appear to exist")
if not _check_export_suffix(src, dest, edited):
logging.warning(
f"Invalid destination suffix: {dest.suffix} for {self.path}, "
+ f"edited={edited}, path_edited={self.path_edited}, "
+ f"original_filename={self.original_filename}, filename={self.filename}"
)
logging.debug( logging.debug(
f"exporting {src} to {dest}, overwrite={overwrite}, increment={increment}, dest exists: {dest.exists()}" f"exporting {src} to {dest}, overwrite={overwrite}, increment={increment}, dest exists: {dest.exists()}"
) )
@@ -757,6 +789,8 @@ def _export_photo(
action depending on update, overwrite action depending on update, overwrite
Assumes destination is the right destination (e.g. UUID matches) Assumes destination is the right destination (e.g. UUID matches)
sets UUID and JSON info foo exported file using set_uuid_for_file, set_inf_for_uuido sets UUID and JSON info foo exported file using set_uuid_for_file, set_inf_for_uuido
Args:
src: src path (string) src: src path (string)
dest: dest path (pathlib.Path) dest: dest path (pathlib.Path)
update: bool update: bool
@@ -766,7 +800,9 @@ def _export_photo(
export_as_hardlink: bool export_as_hardlink: bool
exiftool: bool exiftool: bool
fileutil: FileUtil class that conforms to fileutil.FileUtilABC fileutil: FileUtil class that conforms to fileutil.FileUtilABC
Returns: ExportResults
Returns:
ExportResults
""" """
exported_files = [] exported_files = []

View File

@@ -7,7 +7,7 @@
<key>hostuuid</key> <key>hostuuid</key>
<string>9575E48B-8D5F-5654-ABAC-4431B1167324</string> <string>9575E48B-8D5F-5654-ABAC-4431B1167324</string>
<key>pid</key> <key>pid</key>
<integer>4021</integer> <integer>452</integer>
<key>processname</key> <key>processname</key>
<string>photolibraryd</string> <string>photolibraryd</string>
<key>uid</key> <key>uid</key>

View File

@@ -3,24 +3,24 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>BackgroundHighlightCollection</key> <key>BackgroundHighlightCollection</key>
<date>2020-05-30T01:45:51Z</date> <date>2020-06-06T14:26:31Z</date>
<key>BackgroundHighlightEnrichment</key> <key>BackgroundHighlightEnrichment</key>
<date>2020-05-30T01:45:51Z</date> <date>2020-06-06T14:26:29Z</date>
<key>BackgroundJobAssetRevGeocode</key> <key>BackgroundJobAssetRevGeocode</key>
<date>2020-05-30T04:01:24Z</date> <date>2020-06-06T14:26:31Z</date>
<key>BackgroundJobSearch</key> <key>BackgroundJobSearch</key>
<date>2020-05-30T01:45:51Z</date> <date>2020-06-06T14:26:31Z</date>
<key>BackgroundPeopleSuggestion</key> <key>BackgroundPeopleSuggestion</key>
<date>2020-05-30T01:45:51Z</date> <date>2020-06-06T14:26:29Z</date>
<key>BackgroundUserBehaviorProcessor</key> <key>BackgroundUserBehaviorProcessor</key>
<date>2020-05-29T04:31:38Z</date> <date>2020-06-06T14:26:31Z</date>
<key>PhotoAnalysisGraphLastBackgroundGraphConsistencyUpdateJobDateKey</key> <key>PhotoAnalysisGraphLastBackgroundGraphConsistencyUpdateJobDateKey</key>
<date>2020-05-30T02:16:06Z</date> <date>2020-05-30T02:16:06Z</date>
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key> <key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
<date>2020-05-29T04:31:37Z</date> <date>2020-05-29T04:31:37Z</date>
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key> <key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
<date>2020-05-30T04:01:24Z</date> <date>2020-06-06T14:26:33Z</date>
<key>SiriPortraitDonation</key> <key>SiriPortraitDonation</key>
<date>2020-05-29T04:31:38Z</date> <date>2020-06-06T14:26:31Z</date>
</dict> </dict>
</plist> </plist>

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

View File

@@ -7,6 +7,8 @@ PHOTOS_DB = "tests/Test-10.15.5.photoslibrary/database/photos.db"
PHOTOS_DB_PATH = "/Test-10.15.5.photoslibrary/database/photos.db" PHOTOS_DB_PATH = "/Test-10.15.5.photoslibrary/database/photos.db"
PHOTOS_LIBRARY_PATH = "/Test-10.15.5.photoslibrary" PHOTOS_LIBRARY_PATH = "/Test-10.15.5.photoslibrary"
PHOTOS_DB_LEN = 13
KEYWORDS = [ KEYWORDS = [
"Kids", "Kids",
"wedding", "wedding",
@@ -24,7 +26,7 @@ ALBUMS = [
"Pumpkin Farm", "Pumpkin Farm",
"Test Album", "Test Album",
"AlbumInFolder", "AlbumInFolder",
"Raw" "Raw",
] # Note: there are 2 albums named "Test Album" for testing duplicate album names ] # Note: there are 2 albums named "Test Album" for testing duplicate album names
KEYWORDS_DICT = { KEYWORDS_DICT = {
"Kids": 4, "Kids": 4,
@@ -58,6 +60,7 @@ UUID_DICT = {
"external_edit": "DC99FBDD-7A52-4100-A5BB-344131646C30", "external_edit": "DC99FBDD-7A52-4100-A5BB-344131646C30",
"no_external_edit": "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51", "no_external_edit": "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51",
"export": "D79B8D77-BFFC-460B-9312-034F2877D35B", # "Pumkins2.jpg" "export": "D79B8D77-BFFC-460B-9312-034F2877D35B", # "Pumkins2.jpg"
"export_tif": "8846E3E6-8AC8-4857-8448-E3D025784410",
} }
@@ -126,7 +129,7 @@ def test_db_len():
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
# assert photosdb.db_version in osxphotos._TESTED_DB_VERSIONS # assert photosdb.db_version in osxphotos._TESTED_DB_VERSIONS
assert len(photosdb) == 12 assert len(photosdb) == PHOTOS_DB_LEN
def test_db_version(): def test_db_version():
@@ -378,7 +381,7 @@ def test_count():
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photos = photosdb.photos() photos = photosdb.photos()
assert len(photos) == 12 assert len(photos) == PHOTOS_DB_LEN
def test_keyword_2(): def test_keyword_2():
@@ -730,6 +733,31 @@ def test_export_13():
assert e.type == type(FileNotFoundError()) assert e.type == type(FileNotFoundError())
def test_export_14(caplog):
# test export with user provided filename with different (but valid) extension than source
import os
import os.path
import tempfile
import time
import osxphotos
tempdir = tempfile.TemporaryDirectory(prefix="osxphotos_")
dest = tempdir.name
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photos = photosdb.photos(uuid=[UUID_DICT["export_tif"]])
timestamp = time.time()
filename = f"osxphotos-export-2-test-{timestamp}.tif"
expected_dest = os.path.join(dest, filename)
got_dest = photos[0].export(dest, filename)[0]
assert got_dest == expected_dest
assert os.path.isfile(got_dest)
assert "Invalid destination suffix" not in caplog.text
def test_eq(): def test_eq():
import osxphotos import osxphotos
@@ -781,7 +809,7 @@ def test_from_to_date():
photosdb = osxphotos.PhotosDB(PHOTOS_DB) photosdb = osxphotos.PhotosDB(PHOTOS_DB)
photos = photosdb.photos(from_date=dt.datetime(2018, 10, 28)) photos = photosdb.photos(from_date=dt.datetime(2018, 10, 28))
assert len(photos) ==6 assert len(photos) == 7
photos = photosdb.photos(to_date=dt.datetime(2018, 10, 28)) photos = photosdb.photos(to_date=dt.datetime(2018, 10, 28))
assert len(photos) == 6 assert len(photos) == 6

View File

@@ -164,6 +164,7 @@ CLI_EXPORTED_FILENAME_TEMPLATE_FILENAMES2 = [
"Pumpkin Farm-Pumpkins3.jpg", "Pumpkin Farm-Pumpkins3.jpg",
"Test Album-Pumkins1.jpg", "Test Album-Pumkins1.jpg",
"Test Album-Pumkins2.jpg", "Test Album-Pumkins2.jpg",
"None-IMG_1693.tif",
] ]
CLI_EXPORT_UUID = "D79B8D77-BFFC-460B-9312-034F2877D35B" CLI_EXPORT_UUID = "D79B8D77-BFFC-460B-9312-034F2877D35B"