diff --git a/osxphotos/photoinfo.py b/osxphotos/photoinfo.py index f689f94b..43cfcddc 100644 --- a/osxphotos/photoinfo.py +++ b/osxphotos/photoinfo.py @@ -893,38 +893,37 @@ class PhotoInfo: return photopath - @property + @cached_property def path_derivatives(self): """Return any derivative (preview) images associated with the photo as a list of paths, sorted by file size (largest first)""" - try: - return self._path_derivatives - except AttributeError: - if self._db._db_version <= _PHOTOS_4_VERSION: - self._path_derivatives = self._path_derivatives_4() - return self._path_derivatives + if self._db._db_version <= _PHOTOS_4_VERSION: + return self._path_derivatives_4() - directory = self._uuid[0] # first char of uuid - derivative_path = ( - pathlib.Path(self._db._library_path) - / "resources" - / "derivatives" - / directory - ) - files = derivative_path.glob(f"{self.uuid}*.*") - files = sorted(files, reverse=True, key=lambda f: f.stat().st_size) - # return list of filename but skip .THM files (these are actually low-res thumbnails in JPEG format but with .THM extension) - derivatives = [ - str(filename) for filename in files if filename.suffix != ".THM" - ] - if ( - self.isphoto - and len(derivatives) > 1 - and derivatives[0].endswith(".mov") - ): - derivatives[1], derivatives[0] = derivatives[0], derivatives[1] + if self.shared: + return self._path_derivatives_5_shared() - self._path_derivatives = derivatives - return self._path_derivatives + directory = self._uuid[0] # first char of uuid + derivative_path = ( + pathlib.Path(self._db._library_path) / f"resources/derivatives/{directory}" + ) + files = list(derivative_path.glob(f"{self.uuid}*.*")) + + # previews may be missing from derivatives path + # there are what appear to be low res thumbnails in the "masters" subfolder + thumb_path = ( + pathlib.Path(self._db._library_path) + / f"resources/derivatives/masters/{directory}/{self.uuid}_4_5005_c.jpeg" + ) + if thumb_path.exists(): + files.append(thumb_path) + + files = sorted(files, reverse=True, key=lambda f: f.stat().st_size) + # return list of filename but skip .THM files (these are actually low-res thumbnails in JPEG format but with .THM extension) + derivatives = [str(filename) for filename in files if filename.suffix != ".THM"] + if self.isphoto and len(derivatives) > 1 and derivatives[0].endswith(".mov"): + derivatives[1], derivatives[0] = derivatives[0], derivatives[1] + + return derivatives def _path_derivatives_4(self): """Return paths to all derivative (preview) files for Photos <= 4""" @@ -934,10 +933,7 @@ class PhotoInfo: folder_id, file_id = _get_resource_loc(modelid) derivatives_root = ( pathlib.Path(self._db._library_path) - / "resources" - / "proxies" - / "derivatives" - / folder_id + / f"resources/proxies/derivatives/{folder_id}" ) # photos appears to usually be in "00" subfolder but @@ -962,6 +958,19 @@ class PhotoInfo: # didn't find a derivatives path return [] + def _path_derivatives_5_shared(self): + """Return paths to all derivative (preview) files for shared iCloud photos in Photos >= 5""" + directory = self._uuid[0] # first char of uuid + # only 1 derivative for shared photos and it's called 'UUID_4_5005_c.jpeg' + derivative_path = ( + pathlib.Path(self._db._library_path) + / "resources/cloudsharing/resources/derivatives/masters" + / f"{directory}/{self.uuid}_4_5005_c.jpeg" + ) + if derivative_path.exists(): + return [str(derivative_path)] + return [] + @property def panorama(self): """Returns True if photo is a panorama, otherwise False""" diff --git a/tests/test_bigsur_10_16_0_1.py b/tests/test_bigsur_10_16_0_1.py index aed9afeb..2f57c665 100644 --- a/tests/test_bigsur_10_16_0_1.py +++ b/tests/test_bigsur_10_16_0_1.py @@ -519,6 +519,7 @@ def test_path_derivatives(photosdb): derivs = [ "D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_100_o.jpeg", "D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_105_c.jpeg", + "D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_4_5005_c.jpeg", ] for i, p in enumerate(path): assert p.endswith(derivs[i]) diff --git a/tests/test_catalina_10_15_7.py b/tests/test_catalina_10_15_7.py index 67c6f205..eb764a6b 100644 --- a/tests/test_catalina_10_15_7.py +++ b/tests/test_catalina_10_15_7.py @@ -625,6 +625,7 @@ def test_path_derivatives(photosdb): derivs = [ "D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_100_o.jpeg", "D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_105_c.jpeg", + "D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_4_5005_c.jpeg", ] for i, p in enumerate(path): assert p.endswith(derivs[i]) diff --git a/tests/test_incloud_big_sur_10_16_0.py b/tests/test_incloud_big_sur_10_16_0.py new file mode 100644 index 00000000..a57b354e --- /dev/null +++ b/tests/test_incloud_big_sur_10_16_0.py @@ -0,0 +1,33 @@ +# Test cloud photos + +import pytest + +import osxphotos + +PHOTOS_DB_CLOUD = "tests/Test-Cloud-10.16.0.photoslibrary" + +UUID_DICT = { + "incloud": "FC638F58-84BE-4083-B5DE-F85BDC729062", + "shared": "2094984A-21DC-4A6E-88A6-7344F648B92E", + "cloudasset": "FC638F58-84BE-4083-B5DE-F85BDC729062", +} + + +@pytest.fixture(scope="module") +def photosdb(): + return osxphotos.PhotosDB(dbfile=PHOTOS_DB_CLOUD) + + +def test_incloud(photosdb): + photos = photosdb.photos(uuid=[UUID_DICT["incloud"]]) + assert photos[0].incloud + + +def test_cloudasset_1(photosdb): + photos = photosdb.photos(uuid=[UUID_DICT["cloudasset"]]) + assert photos[0].iscloudasset + + +def test_path_derivatives(photosdb): + photo = photosdb.get_photo(UUID_DICT["shared"]) + assert photo.path_derivatives diff --git a/tests/test_incloud_catalina_10_15_6.py b/tests/test_incloud_catalina_10_15_6.py index 24d6b4d9..d6a5d296 100644 --- a/tests/test_incloud_catalina_10_15_6.py +++ b/tests/test_incloud_catalina_10_15_6.py @@ -12,6 +12,7 @@ UUID_DICT = { "not_incloud": "33000A44-E4BA-43A3-9304-62A0195AB4D9", "cloudasset": "D11D25FF-5F31-47D2-ABA9-58418878DC15", "not_cloudasset": "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4", + "shared": "4AD7C8EF-2991-4519-9D3A-7F44A6F031BE", } @@ -52,3 +53,8 @@ def test_cloudasset_3(): photos = photosdb.photos(uuid=[UUID_DICT["not_cloudasset"]]) assert not photos[0].iscloudasset + + +def test_path_derivatives(photosdb): + photo = photosdb.get_photo(UUID_DICT["shared"]) + assert photo.path_derivatives diff --git a/tests/test_monterey_12_0_1.py b/tests/test_monterey_12_0_1.py index 5ba9f02e..77dcc9c8 100644 --- a/tests/test_monterey_12_0_1.py +++ b/tests/test_monterey_12_0_1.py @@ -524,6 +524,7 @@ def test_path_derivatives(photosdb): derivs = [ "D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_100_o.jpeg", "D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_105_c.jpeg", + "D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_4_5005_c.jpeg", ] for i, p in enumerate(path): assert p.endswith(derivs[i]) diff --git a/tests/test_monterey_dev_beta_12_0_0.py b/tests/test_monterey_dev_beta_12_0_0.py index dbc5e8e8..d69a0a25 100644 --- a/tests/test_monterey_dev_beta_12_0_0.py +++ b/tests/test_monterey_dev_beta_12_0_0.py @@ -579,6 +579,7 @@ def test_path_derivatives(photosdb): derivs = [ "D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_100_o.jpeg", "D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_1_105_c.jpeg", + "D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068_4_5005_c.jpeg", ] for i, p in enumerate(path): assert p.endswith(derivs[i])