Added incloud and iscloudasset to PhotoInfo (Photos 5)

This commit is contained in:
Rhet Turnbull
2020-01-11 08:10:28 -08:00
parent 5473f3b3fd
commit 24b43b5e4d
4 changed files with 114 additions and 8 deletions

View File

@@ -47,6 +47,8 @@
- [`shared`](#shared)
- [`isphoto`](#isphoto)
- [`ismovie`](#ismovie)
- [`iscloudasset`](#iscloudasset)
- [`incloud`](#incloud)
- [`uti`](#uti)
- [`burst`](#burst)
- [`burst_photos`](#burst_photos)
@@ -66,7 +68,7 @@
* [Implementation Notes](#implementation-notes)
* [Dependencies](#dependencies)
* [Acknowledgements](#acknowledgements)
## What is osxphotos?
OSXPhotos provides the ability to interact with and query Apple's Photos.app library database on MacOS. Using this module you can query the Photos database for information about the photos stored in a Photos library on your Mac--for example, file name, file path, and metadata such as keywords/tags, persons/faces, albums, etc. You can also easily export both the original and edited photos.
@@ -597,6 +599,12 @@ Returns True if type is photo/still image, otherwise False
#### `ismovie`
Returns True if type is movie/video, otherwise False
#### `iscloudasset`
Returns True if photo is a cloud asset, that is, it is in a library synched to iCloud. See also [incloud](#incloud)
#### `incloud`
Returns True if photo is a [cloud asset](#iscloudasset) and is synched to iCloud otherwise False if photo is a cloud asset and not yet synched to iCloud. Returns None if photo is not a cloud asset.
#### `uti`
Returns Uniform Type Identifier (UTI) for the image, for example: 'public.jpeg' or 'com.apple.quicktime-movie'

View File

@@ -312,6 +312,21 @@ class PhotoInfo:
"""
return True if self._info["type"] == _PHOTO_TYPE else False
@property
def incloud(self):
""" Returns True if photo is cloud asset and is synched to cloud
False if photo is cloud asset and not yet synched to cloud
None if photo is not cloud asset
"""
return self._info["incloud"]
@property
def iscloudasset(self):
""" Returns True if photo is a cloud asset (in an iCloud library),
otherwise False
"""
return True if self._info["cloudAssetGUID"] is not None else False
@property
def burst(self):
""" Returns True if photo is part of a Burst photo set, otherwise False """

View File

@@ -628,6 +628,12 @@ class PhotosDB:
# TODO: Handle selfies (front facing camera, RKVersion.selfPortrait == 1)
# self._dbphotos[uuid]["selfie"] = True if row[27] == 1 else False
self._dbphotos[uuid]["selfie"] = None
# Init cloud details that will be filled in later
self._dbphotos[uuid]["cloudAssetGUID"] = None
self._dbphotos[uuid]["cloudLocalState"] = None # will be initialized later if is cloud asset
self._dbphotos[uuid]["incloud"] = None # will be initialized later if is cloud asset
# get details needed to find path of the edited photos
c.execute(
@@ -929,7 +935,8 @@ class PhotosDB:
ZGENERICASSET.ZAVALANCHEPICKTYPE,
ZGENERICASSET.ZKINDSUBTYPE,
ZGENERICASSET.ZCUSTOMRENDEREDVALUE,
ZADDITIONALASSETATTRIBUTES.ZCAMERACAPTUREDEVICE
ZADDITIONALASSETATTRIBUTES.ZCAMERACAPTUREDEVICE,
ZGENERICASSET.ZCLOUDASSETGUID
FROM ZGENERICASSET
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = ZGENERICASSET.Z_PK
WHERE ZGENERICASSET.ZTRASHEDSTATE = 0
@@ -960,6 +967,9 @@ class PhotosDB:
# 21 ZGENERICASSET.ZKINDSUBTYPE -- determine if live photos, etc
# 22 ZGENERICASSET.ZCUSTOMRENDEREDVALUE -- determine if HDR photo
# 23 ZADDITIONALASSETATTRIBUTES.ZCAMERACAPTUREDEVICE -- 1 if selfie (front facing camera)
# 25 ZGENERICASSET.ZCLOUDASSETGUID -- not null if asset is cloud asset
# (e.g. user has "iCloud Photos" checked in Photos preferences)
for row in c:
@@ -1070,6 +1080,11 @@ class PhotosDB:
# Handle selfies (front facing camera, ZCAMERACAPTUREDEVICE=1)
info["selfie"] = True if row[23] == 1 else False
# Determine if photo is part of cloud library (ZGENERICASSET.ZCLOUDASSETGUID not NULL)
info["cloudAssetGUID"] = row[24]
info["cloudLocalState"] = None # will be initialized later if is cloud asset
info["incloud"] = None # will be initialized later if is cloud asset
self._dbphotos[uuid] = info
# # if row[19] is not None and ((row[20] == 2) or (row[20] == 4)):
@@ -1131,9 +1146,6 @@ class PhotosDB:
# Get info on remote/local availability for photos in shared albums
# Shared photos have a null fingerprint (and some other photos do too)
# TODO: There may be a bug here, perhaps ZDATASTORESUBTYPE should be 1 --> it's the longest ZDATALENGTH (is this the original)
# Also, doesn't seem to be entirely accurate for PNGs (screenshots mostly)
# for PNGs, JPEG render seems to be used unless edited or exported
# see for example ./resources/renders/F/F2FF9B89-FB6F-4853-942B-9F8BEE8DFFA1_1_201_a.jpeg
c.execute(
""" SELECT
ZGENERICASSET.ZUUID,
@@ -1195,8 +1207,19 @@ class PhotosDB:
# f"{uuid} isMissing changed: {old} {self._dbphotos[uuid]['isMissing']}"
# )
if _debug():
logging.debug(pformat(self._dbphotos))
# get information about cloud sync state
c.execute(
""" SELECT
ZGENERICASSET.ZUUID,
ZCLOUDMASTER.ZCLOUDLOCALSTATE
FROM ZCLOUDMASTER, ZGENERICASSET
WHERE ZGENERICASSET.ZMASTER = ZCLOUDMASTER.Z_PK """
)
for row in c:
uuid = row[0]
if uuid in self._dbphotos:
self._dbphotos[uuid]["cloudLocalState"] = row[1]
self._dbphotos[uuid]["incloud"] = True if row[1] == 3 else False
# add faces and keywords to photo data
for uuid in self._dbphotos:
@@ -1226,6 +1249,7 @@ class PhotosDB:
conn.close()
self._cleanup_tmp_files()
# done processing, dump debug data if requested
if _debug():
logging.debug("Faces:")
logging.debug(pformat(self._dbfaces_uuid))
@@ -1254,7 +1278,6 @@ class PhotosDB:
logging.debug("Burst Photos:")
logging.debug(pformat(self._dbphotos_burst))
# TODO: fix default values to None instead of []
def photos(
self,
keywords=None,

60
tests/test_incloud.py Normal file
View File

@@ -0,0 +1,60 @@
# Test cloud photos
import pytest
PHOTOS_DB_CLOUD = "./tests/Test-Cloud-10.15.1.photoslibrary/database/photos.db"
PHOTOS_DB_NOT_CLOUD = "./tests/Test-10.15.1.photoslibrary/database/photos.db"
UUID_DICT = {
"incloud": "37210110-E940-4227-92D3-45C40F68EB0A",
"not_incloud": "E5BC411D-30EE-44D3-84C0-54760A10579D",
"cloudasset": "D11D25FF-5F31-47D2-ABA9-58418878DC15",
"not_cloudasset": "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4",
}
def test_incloud():
import osxphotos
photosdb = osxphotos.PhotosDB(PHOTOS_DB_CLOUD)
photos = photosdb.photos(uuid=[UUID_DICT["incloud"]])
assert photos[0].incloud
def test_not_incloud():
import osxphotos
photosdb = osxphotos.PhotosDB(PHOTOS_DB_CLOUD)
photos = photosdb.photos(uuid=[UUID_DICT["not_incloud"]])
assert not photos[0].incloud
def test_cloudasset_1():
import osxphotos
photosdb = osxphotos.PhotosDB(PHOTOS_DB_CLOUD)
photos = photosdb.photos(uuid=[UUID_DICT["cloudasset"]])
assert photos[0].iscloudasset
def test_cloudasset_2():
import osxphotos
photosdb = osxphotos.PhotosDB(PHOTOS_DB_CLOUD)
photos = photosdb.photos(uuid=[UUID_DICT["not_incloud"]])
# not_incloud is still a cloud asset
assert photos[0].iscloudasset
def test_cloudasset_3():
import osxphotos
photosdb = osxphotos.PhotosDB(PHOTOS_DB_NOT_CLOUD)
photos = photosdb.photos(uuid=[UUID_DICT["not_cloudasset"]])
assert not photos[0].iscloudasset