Partial fix for #859, missing path edited on Mojave (#862)

* Partial fix for #859, missing path edited on Mojave

* Fixed annotation issue
This commit is contained in:
Rhet Turnbull 2022-12-11 11:19:11 -08:00 committed by GitHub
parent bb65765afa
commit 3557658b73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 109 additions and 76 deletions

View File

@ -4,6 +4,8 @@ Represents a single photo in the Photos library and provides access to the photo
PhotosDB.photos() returns a list of PhotoInfo objects
"""
from __future__ import annotations
import contextlib
import dataclasses
import datetime
@ -277,77 +279,106 @@ class PhotoInfo:
return photopath
def _path_edited_4(self):
"""return path_edited for Photos <= 4"""
def _get_predicted_path_edited_4(self) -> str | None:
"""return predicted path_edited for Photos <= 4"""
edit_id = self._info["edit_resource_id"]
folder_id, file_id, nn_id = _get_resource_loc(edit_id)
# figure out what kind it is and build filename
library = self._db._library_path
type_ = self._info["type"]
if type_ == _PHOTO_TYPE:
# it's a photo
filename = f"fullsizeoutput_{file_id}.jpeg"
elif type_ == _MOVIE_TYPE:
# it's a movie
filename = f"fullsizeoutput_{file_id}.mov"
else:
raise ValueError(f"Unknown type {type_}")
if self._db._db_version > _PHOTOS_4_VERSION:
raise RuntimeError("Wrong database format!")
return os.path.join(
library, "resources", "media", "version", folder_id, nn_id, filename
)
photopath = None
if self._info["hasAdjustments"]:
edit_id = self._info["edit_resource_id"]
if edit_id is not None:
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
# figure out what kind it is and build filename
filename = None
if self._info["type"] == _PHOTO_TYPE:
# it's a photo
filename = f"fullsizeoutput_{file_id}.jpeg"
elif self._info["type"] == _MOVIE_TYPE:
# it's a movie
filename = f"fullsizeoutput_{file_id}.mov"
else:
# don't know what it is!
logging.debug(f"WARNING: unknown type {self._info['type']}")
return None
def _path_edited_4(self) -> str | None:
"""return path_edited for Photos <= 4; modified version of code in PhotoInfo to debug #859"""
# photopath appears to usually be in "00" subfolder but
# could be elsewhere--I haven't figured out this logic yet
# first see if it's in 00
photopath = os.path.join(
library, "resources", "media", "version", folder_id, "00", filename
)
if not self._info["hasAdjustments"]:
return None
if not os.path.isfile(photopath):
rootdir = os.path.join(
library, "resources", "media", "version", folder_id
)
if edit_id := self._info["edit_resource_id"]:
try:
photopath = self._get_predicted_path_edited_4()
except ValueError as e:
logging.debug(f"ERROR: {e}")
photopath = None
for dirname, _, filelist in os.walk(rootdir):
if filename in filelist:
photopath = os.path.join(dirname, filename)
break
if photopath is not None and not os.path.isfile(photopath):
# the heuristic failed, so try to find the file
rootdir = pathlib.Path(photopath).parent.parent
filename = pathlib.Path(photopath).name
for dirname, _, filelist in os.walk(rootdir):
if filename in filelist:
photopath = os.path.join(dirname, filename)
break
# check again to see if we found a valid file
if not os.path.isfile(photopath):
logging.debug(
f"MISSING PATH: edited file for UUID {self._uuid} should be at {photopath} but does not appear to exist"
)
photopath = None
else:
# check again to see if we found a valid file
if photopath is not None and not os.path.isfile(photopath):
logging.debug(
f"{self.uuid} hasAdjustments but edit_resource_id is None"
f"MISSING PATH: edited file for UUID {self._uuid} should be at {photopath} but does not appear to exist"
)
photopath = None
else:
logging.debug(f"{self.uuid} hasAdjustments but edit_resource_id is None")
photopath = None
return photopath
@property
def path_edited_live_photo(self):
"""return path to edited version of live photo movie; only valid for Photos 5+"""
if self._db._db_version < _PHOTOS_5_VERSION:
return None
"""return path to edited version of live photo movie"""
try:
return self._path_edited_live_photo
except AttributeError:
self._path_edited_live_photo = self._path_edited_5_live_photo()
if self._db._db_version < _PHOTOS_5_VERSION:
self._path_edited_live_photo = self._path_edited_4_live_photo()
else:
self._path_edited_live_photo = self._path_edited_5_live_photo()
return self._path_edited_live_photo
def _get_predicted_path_edited_live_photo_4(self) -> str | None:
"""return predicted path_edited for Photos <= 4"""
edit_id = self._info["edit_resource_id"]
folder_id, file_id, nn_id = _get_resource_loc(edit_id)
# figure out what kind it is and build filename
library = self._db._library_path
filename = f"videocomplementoutput_{file_id}.mov"
return os.path.join(
library, "resources", "media", "version", folder_id, nn_id, filename
)
def _path_edited_4_live_photo(self):
"""return path_edited_live_photo for Photos <= 4"""
if self._db._db_version > _PHOTOS_4_VERSION:
raise RuntimeError("Wrong database format!")
photopath = self._get_predicted_path_edited_live_photo_4()
if not os.path.isfile(photopath):
# the heuristic failed, so try to find the file
rootdir = pathlib.Path(photopath).parent.parent
filename = pathlib.Path(photopath).name
photopath = next(
(
os.path.join(dirname, filename)
for dirname, _, filelist in os.walk(rootdir)
if filename in filelist
),
None,
)
if photopath is None:
logging.debug(
f"MISSING PATH: edited live photo file for UUID {self._uuid} does not appear to exist"
)
return photopath
def _path_edited_5_live_photo(self):
"""return path_edited_live_photo for Photos >= 5"""
if self._db._db_version < _PHOTOS_5_VERSION:
@ -863,7 +894,7 @@ class PhotoInfo:
logging.debug(f"missing live_model_id: {self._uuid}")
photopath = None
else:
folder_id, file_id = _get_resource_loc(live_model_id)
folder_id, file_id, nn_id = _get_resource_loc(live_model_id)
library_path = self._db.library_path
photopath = os.path.join(
library_path,
@ -871,7 +902,7 @@ class PhotoInfo:
"media",
"master",
folder_id,
"00",
nn_id,
f"jpegvideocomplement_{file_id}.mov",
)
if not os.path.isfile(photopath):
@ -934,17 +965,13 @@ class PhotoInfo:
modelid = self._info["modelID"]
if modelid is None:
return []
folder_id, file_id = _get_resource_loc(modelid)
folder_id, file_id, nn_id = _get_resource_loc(modelid)
derivatives_root = (
pathlib.Path(self._db._library_path)
/ f"resources/proxies/derivatives/{folder_id}"
)
# photos appears to usually be in "00" subfolder but
# could be elsewhere--I haven't figured out this logic yet
# first see if it's in 00
derivatives_path = derivatives_root / "00" / file_id
derivatives_path = derivatives_root / nn_id / file_id
if derivatives_path.is_dir():
files = derivatives_path.glob("*")
files = sorted(files, reverse=True, key=lambda f: f.stat().st_size)

View File

@ -1297,7 +1297,9 @@ class PhotosDB:
RKModelResource.resourceTag, RKModelResource.UTI, RKVersion.specialType,
RKModelResource.attachedModelType, RKModelResource.resourceType
FROM RKVersion
JOIN RKModelResource on RKModelResource.attachedModelId = RKVersion.modelId """
JOIN RKModelResource on RKModelResource.attachedModelId = RKVersion.modelId
ORDER BY RKModelResource.modelId
"""
)
# Order of results:
@ -1307,8 +1309,8 @@ class PhotosDB:
# 3 RKModelResource.resourceTag
# 4 RKModelResource.UTI
# 5 RKVersion.specialType
# 6 RKModelResource.attachedModelType
# 7 RKModelResource.resourceType
# 6 RKModelResource.attachedModelType (2 = edit)
# 7 RKModelResource.resourceType (4 = photo, 8 = video)
for row in c:
uuid = row[0]
@ -1326,10 +1328,8 @@ class PhotosDB:
f"WARNING: found more than one edit_resource_id for "
f"UUID {row[0]},adjustmentUUID {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?
# For now, return most recent edit
# Sometimes the library has multiple edits for a photo
# Not sure why, but we'll just use the most recent one
self._dbphotos[uuid]["edit_resource_id"] = row[2]
self._dbphotos[uuid]["UTI_edited"] = row[4]
@ -1792,7 +1792,8 @@ class PhotosDB:
"parentfolder": album[7],
"pk": album[8],
"intrash": False if album[9] == 0 else True,
"creation_date": album[10] or 0, # iPhone Photos.sqlite can have null value
"creation_date": album[10]
or 0, # iPhone Photos.sqlite can have null value
"start_date": album[11] or 0,
"end_date": album[12] or 0,
"customsortascending": album[13],

View File

@ -101,22 +101,27 @@ def _check_file_exists(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 """
def _get_resource_loc(model_id) -> tuple[str, str, str]:
"""returns folder_id and file_id needed to find location of edited photo
and live photos for version <= Photos 4.0
modified version of code in utils to debug #859
"""
# determine folder where Photos stores edited version
# edited images are stored in:
# Photos Library.photoslibrary/resources/media/version/XX/00/fullsizeoutput_Y.jpeg
# Photos Library.photoslibrary/resources/media/version/folder_id/nn/fullsizeoutput_file_id.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
# folder_id (XX) is digits -4 and -3 of hex representation of model_id
# and left padded with zeros if < 4 digits
folder_id = hex_id.zfill(4)[0:2]
folder_id = hex_id.zfill(4)[-4:-2]
return folder_id, file_id
# find the nn_id which is the hex_id digits minus the last 4 chars (or 00 if len(hex_id) <= 4)
nn_id = hex_id[: len(hex_id) - 4].zfill(2) if len(hex_id) > 4 else "00"
return folder_id, file_id, nn_id
def _dd_to_dms(dd):

View File

@ -255,7 +255,7 @@ UUID_NOT_REFERENCE = "F12384F6-CD17-4151-ACBA-AE0E3688539E"
UUID_DUPLICATE = ""
UUID_DETECTED_TEXT = {
"E2078879-A29C-4D6F-BACB-E3BBE6C3EB91": "OPEN",
"E2078879-A29C-4D6F-BACB-E3BBE6C3EB91": " ",
"A92D9C26-3A50-4197-9388-CB5F7DB9FA91": None,
}

View File

@ -325,8 +325,8 @@ TEMPLATE_VALUES_DATE_NOT_MODIFIED = {
UUID_DETECTED_TEXT = "E2078879-A29C-4D6F-BACB-E3BBE6C3EB91"
TEMPLATE_VALUES_DETECTED_TEXT = {
"{detected_text}": "OPEN",
"{;+detected_text:0.5}": "OPEN",
"{detected_text}": " ",
"{;+detected_text:0.5}": " ",
}
COMMENT_UUID_DICT = {