* Partial fix for #859, missing path edited on Mojave * Fixed annotation issue
This commit is contained in:
parent
bb65765afa
commit
3557658b73
@ -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)
|
||||
|
||||
@ -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],
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
|
||||
@ -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 = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user