Fixed path_edited() for Photos 4.0
This commit is contained in:
@@ -331,7 +331,7 @@ Returns a list of the names of the persons in the photo
|
|||||||
Returns the absolute path to the photo on disk as a string. Note: this returns the path to the *original* unedited file (see `hasadjustments()`). If the file is missing on disk, path=`None` (see `ismissing()`)
|
Returns the absolute path to the photo on disk as a string. Note: this returns the path to the *original* unedited file (see `hasadjustments()`). If the file is missing on disk, path=`None` (see `ismissing()`)
|
||||||
|
|
||||||
#### `path_edited()`
|
#### `path_edited()`
|
||||||
Returns the absolute path to the edited photo on disk as a string. If the photo has not been edited, returns `None`. See also `path()` and `hasadjustments()`. Note: Currently only implemented for Photos 5.0+ (MacOS 10.15); returns `None` on previous versions.
|
Returns the absolute path to the edited photo on disk as a string. If the photo has not been edited, returns `None`. See also `path()` and `hasadjustments()`.
|
||||||
|
|
||||||
#### `ismissing()`
|
#### `ismissing()`
|
||||||
Returns `True` if the original image file is missing on disk, otherwise `False`. This can occur if the file has been uploaded to iCloud but not yet downloaded to the local library or if the file was deleted or imported from a disk that has been unmounted. Note: this status is set by Photos and osxphotos does not verify that the file path returned by `path()` actually exists. It merely reports what Photos has stored in the library database.
|
Returns `True` if the original image file is missing on disk, otherwise `False`. This can occur if the file has been uploaded to iCloud but not yet downloaded to the local library or if the file was deleted or imported from a disk that has been unmounted. Note: this status is set by Photos and osxphotos does not verify that the file path returned by `path()` actually exists. It merely reports what Photos has stored in the library database.
|
||||||
|
|||||||
@@ -81,6 +81,24 @@ def _check_file_exists(filename):
|
|||||||
return os.path.exists(filename) and not os.path.isdir(filename)
|
return os.path.exists(filename) and not os.path.isdir(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_resource_loc(model_id):
|
||||||
|
""" returns folder_id and file_id needed to find location of edited photo """
|
||||||
|
""" and live photos for version <= Photos 4.0 """
|
||||||
|
# determine folder where Photos stores edited version
|
||||||
|
# edited images are stored in:
|
||||||
|
# Photos Library.photoslibrary/resources/media/version/XX/00/fullsizeoutput_Y.jpeg
|
||||||
|
# where XX and Y are computed based on RKModelResources.modelId
|
||||||
|
|
||||||
|
# file_id (Y in above example) is hex representation of model_id without leading 0x
|
||||||
|
file_id = hex_id = hex(model_id)[2:]
|
||||||
|
|
||||||
|
# folder_id (XX) in above example if first two chars of model_id converted to hex
|
||||||
|
# and left padded with zeros if < 4 digits
|
||||||
|
folder_id = hex_id.zfill(4)[0:2]
|
||||||
|
|
||||||
|
return folder_id, file_id
|
||||||
|
|
||||||
|
|
||||||
class PhotosDB:
|
class PhotosDB:
|
||||||
def __init__(self, dbfile=None):
|
def __init__(self, dbfile=None):
|
||||||
""" create a new PhotosDB object """
|
""" create a new PhotosDB object """
|
||||||
@@ -397,18 +415,6 @@ class PhotosDB:
|
|||||||
|
|
||||||
(conn, c) = self._open_sql_file(self._tmp_db)
|
(conn, c) = self._open_sql_file(self._tmp_db)
|
||||||
|
|
||||||
# if int(self._db_version) > int(_PHOTOS_5_VERSION):
|
|
||||||
# # need to close the photos.db database and re-open Photos.sqlite
|
|
||||||
# c.close()
|
|
||||||
# try:
|
|
||||||
# os.remove(tmp_db)
|
|
||||||
# except:
|
|
||||||
# print("Could not remove temporary database: " + tmp_db, file=sys.stderr)
|
|
||||||
|
|
||||||
# self._dbfile2 = Path(self._dbfile) "Photos.sqlite"
|
|
||||||
# tmp_db = self._copy_db_file(fname)
|
|
||||||
# (conn, c) = self._open_sql_file(tmp_db)
|
|
||||||
|
|
||||||
# Look for all combinations of persons and pictures
|
# Look for all combinations of persons and pictures
|
||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
@@ -500,10 +506,35 @@ class PhotosDB:
|
|||||||
+ "RKVersion.hasAdjustments, RKVersion.hasKeywords, RKVersion.imageTimeZoneOffsetSeconds, "
|
+ "RKVersion.hasAdjustments, RKVersion.hasKeywords, RKVersion.imageTimeZoneOffsetSeconds, "
|
||||||
+ "RKMaster.volumeId, RKMaster.imagePath, RKVersion.extendedDescription, RKVersion.name, "
|
+ "RKMaster.volumeId, RKMaster.imagePath, RKVersion.extendedDescription, RKVersion.name, "
|
||||||
+ "RKMaster.isMissing, RKMaster.originalFileName, RKVersion.isFavorite, RKVersion.isHidden, "
|
+ "RKMaster.isMissing, RKMaster.originalFileName, RKVersion.isFavorite, RKVersion.isHidden, "
|
||||||
+ "RKVersion.latitude, RKVersion.longitude "
|
+ "RKVersion.latitude, RKVersion.longitude, "
|
||||||
|
+ "RKVersion.adjustmentUuid "
|
||||||
+ "from RKVersion, RKMaster where RKVersion.isInTrash = 0 and RKVersion.type = 2 and "
|
+ "from RKVersion, RKMaster where RKVersion.isInTrash = 0 and RKVersion.type = 2 and "
|
||||||
+ "RKVersion.masterUuid = RKMaster.uuid and RKVersion.filename not like '%.pdf'"
|
+ "RKVersion.masterUuid = RKMaster.uuid and RKVersion.filename not like '%.pdf'"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# order of results
|
||||||
|
# 0 RKVersion.uuid
|
||||||
|
# 1 RKVersion.modelId
|
||||||
|
# 2 RKVersion.masterUuid
|
||||||
|
# 3 RKVersion.filename
|
||||||
|
# 4 RKVersion.lastmodifieddate
|
||||||
|
# 5 RKVersion.imageDate
|
||||||
|
# 6 RKVersion.mainRating
|
||||||
|
# 7 RKVersion.hasAdjustments
|
||||||
|
# 8 RKVersion.hasKeywords
|
||||||
|
# 9 RKVersion.imageTimeZoneOffsetSeconds
|
||||||
|
# 10 RKMaster.volumeId
|
||||||
|
# 11 RKMaster.imagePath
|
||||||
|
# 12 RKVersion.extendedDescription
|
||||||
|
# 13 RKVersion.name
|
||||||
|
# 14 RKMaster.isMissing
|
||||||
|
# 15 RKMaster.originalFileName
|
||||||
|
# 16 RKVersion.isFavorite
|
||||||
|
# 17 RKVersion.isHidden
|
||||||
|
# 18 RKVersion.latitude
|
||||||
|
# 19 RKVersion.longitude
|
||||||
|
# 20 RKVersion.adjustmentUuid
|
||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
for row in c:
|
for row in c:
|
||||||
i = i + 1
|
i = i + 1
|
||||||
@@ -540,6 +571,57 @@ class PhotosDB:
|
|||||||
self._dbphotos[uuid]["hidden"] = row[17]
|
self._dbphotos[uuid]["hidden"] = row[17]
|
||||||
self._dbphotos[uuid]["latitude"] = row[18]
|
self._dbphotos[uuid]["latitude"] = row[18]
|
||||||
self._dbphotos[uuid]["longitude"] = row[19]
|
self._dbphotos[uuid]["longitude"] = row[19]
|
||||||
|
self._dbphotos[uuid]["adjustmentUuid"] = row[20]
|
||||||
|
|
||||||
|
# get details needed to find path of the edited photos and live photos
|
||||||
|
c.execute(
|
||||||
|
"SELECT RKVersion.uuid, RKVersion.adjustmentUuid, RKModelResource.modelId, "
|
||||||
|
"RKModelResource.resourceTag, RKModelResource.UTI, RKVersion.specialType, "
|
||||||
|
"RKModelResource.attachedModelType, RKModelResource.resourceType "
|
||||||
|
"FROM RKVersion "
|
||||||
|
"JOIN RKModelResource on RKModelResource.attachedModelId = RKVersion.modelId "
|
||||||
|
"WHERE RKVersion.isInTrash = 0 "
|
||||||
|
)
|
||||||
|
|
||||||
|
# Order of results:
|
||||||
|
# 0 RKVersion.uuid
|
||||||
|
# 1 RKVersion.adjustmentUuid
|
||||||
|
# 2 RKModelResource.modelId
|
||||||
|
# 3 RKModelResource.resourceTag
|
||||||
|
# 4 RKModelResource.UTI
|
||||||
|
# 5 RKVersion.specialType
|
||||||
|
# 7 RKModelResource.attachedModelType
|
||||||
|
# 8 RKModelResource.resourceType
|
||||||
|
|
||||||
|
# TODO: add live photos
|
||||||
|
# attachedmodeltype is 2, it's a photo, could be more than one
|
||||||
|
# if 5, it's a facetile
|
||||||
|
# specialtype = 0 == image, 5 or 8 == live photo movie
|
||||||
|
|
||||||
|
for row in c:
|
||||||
|
uuid = row[0]
|
||||||
|
if uuid in self._dbphotos:
|
||||||
|
if self._dbphotos[uuid]["adjustmentUuid"] == row[3]:
|
||||||
|
if (
|
||||||
|
row[1] != "UNADJUSTEDNONRAW"
|
||||||
|
and row[1] != "UNADJUSTED"
|
||||||
|
and row[4] == "public.jpeg"
|
||||||
|
and row[6] == 2
|
||||||
|
):
|
||||||
|
if "edit_resource_id" in self._dbphotos[uuid]:
|
||||||
|
logging.warning(
|
||||||
|
f"WARNING: found more than one edit_resource_id for "
|
||||||
|
f"UUID {row[0]},adjustmentID {row[1]}, modelID {row[2]}"
|
||||||
|
)
|
||||||
|
# TODO: I think there should never be more than one edit but
|
||||||
|
# I've seen this once in my library
|
||||||
|
# should we return all edits or just most recent one?
|
||||||
|
self._dbphotos[uuid]["edit_resource_id"] = row[2]
|
||||||
|
|
||||||
|
# init any uuids that had no edits
|
||||||
|
for uuid in self._dbphotos:
|
||||||
|
if "edit_resource_id" not in self._dbphotos[uuid]:
|
||||||
|
self._dbphotos[uuid]["edit_resource_id"] = None
|
||||||
|
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
@@ -1047,11 +1129,33 @@ class PhotoInfo:
|
|||||||
photopath = ""
|
photopath = ""
|
||||||
|
|
||||||
if self.__db._db_version < _PHOTOS_5_VERSION:
|
if self.__db._db_version < _PHOTOS_5_VERSION:
|
||||||
# TODO: implement this
|
if self.__info["hasAdjustments"]:
|
||||||
photopath = None
|
edit_id = self.__info["edit_resource_id"]
|
||||||
logging.debug(
|
if edit_id is not None:
|
||||||
"WARNING: path_edited not implemented yet for this database version"
|
library = self.__db._library_path
|
||||||
)
|
folder_id, file_id = _get_resource_loc(edit_id)
|
||||||
|
# todo: is this always true or do we need to search file file_id under folder_id
|
||||||
|
photopath = os.path.join(
|
||||||
|
library,
|
||||||
|
"resources",
|
||||||
|
"media",
|
||||||
|
"version",
|
||||||
|
folder_id,
|
||||||
|
"00",
|
||||||
|
f"fullsizeoutput_{file_id}.jpeg",
|
||||||
|
)
|
||||||
|
if not os.path.isfile(photopath):
|
||||||
|
logging.warning(
|
||||||
|
f"edited file for UUID {self.__uuid} should be at {photopath} but does not appear to exist"
|
||||||
|
)
|
||||||
|
photopath = None
|
||||||
|
else:
|
||||||
|
logging.warning(
|
||||||
|
f"{self.uuid} hasAdjustments but edit_model_id is None"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
photopath = None
|
||||||
|
|
||||||
# if self.__info["isMissing"] == 1:
|
# if self.__info["isMissing"] == 1:
|
||||||
# photopath = None # path would be meaningless until downloaded
|
# photopath = None # path would be meaningless until downloaded
|
||||||
else:
|
else:
|
||||||
@@ -1077,7 +1181,7 @@ class PhotoInfo:
|
|||||||
|
|
||||||
if not os.path.isfile(photopath):
|
if not os.path.isfile(photopath):
|
||||||
logging.warning(
|
logging.warning(
|
||||||
f"WARNING: edited file should be at {photopath} but does not appear to exist"
|
f"edited file for UUID {self.__uuid} should be at {photopath} but does not appear to exist"
|
||||||
)
|
)
|
||||||
photopath = None
|
photopath = None
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -238,11 +238,7 @@ def test_path_edited1():
|
|||||||
assert len(photos) == 1
|
assert len(photos) == 1
|
||||||
p = photos[0]
|
p = photos[0]
|
||||||
path = p.path_edited()
|
path = p.path_edited()
|
||||||
assert path is None
|
assert path.endswith("resources/media/version/00/00/fullsizeoutput_9.jpeg")
|
||||||
# TODO: update when implemented
|
|
||||||
# assert path.endswith(
|
|
||||||
# "resources/renders/E/E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51_1_201_a.jpeg"
|
|
||||||
# )
|
|
||||||
|
|
||||||
|
|
||||||
def test_path_edited2():
|
def test_path_edited2():
|
||||||
|
|||||||
Reference in New Issue
Block a user