parent
25d4015b65
commit
4efc0c9f56
@ -1181,12 +1181,12 @@ Returns True if photo is a [cloud asset](#iscloudasset) and is synched to iCloud
|
||||
|
||||
#### `syndicated`
|
||||
|
||||
Return true if photo was shared via syndication (e.g. via Messages, etc.); these are photos that appear in "Shared with you" album. Photos 8+ only; returns None if not Photos 8+.
|
||||
Return true if photo was shared via syndication (e.g. via Messages, etc.); these are photos that appear in "Shared with you" album. Photos 7+ only; returns None if not Photos 7+.
|
||||
|
||||
#### `saved_to_library`
|
||||
|
||||
Return True if syndicated photo has been saved to library; returns False if photo is not syndicated or has not been saved to the library.
|
||||
Syndicated photos are photos that appear in "Shared with you" album. Photos 8+ only; returns None if not Photos 8+.
|
||||
Syndicated photos are photos that appear in "Shared with you" album. Photos 7+ only; returns None if not Photos 7+.
|
||||
|
||||
### `shared_moment`
|
||||
|
||||
|
||||
@ -52,6 +52,9 @@ _PHOTOS_7_MODEL_VERSION = [15000, 15999] # Dev preview: 15134, 12.1: 15331
|
||||
_PHOTOS_8_MODEL_VERSION = [16000, 16999] # Ventura dev preview: 16119
|
||||
_PHOTOS_9_MODEL_VERSION = [17000, 17999] # Sonoma dev preview: 17120
|
||||
|
||||
# the preview versions of 12.0.0 had a difference schema for syndication info so need to check model version before processing
|
||||
_PHOTOS_SYNDICATION_MODEL_VERSION = 15323 # 12.0.1
|
||||
|
||||
# some table names differ between Photos 5 and later versions
|
||||
_DB_TABLE_NAMES = {
|
||||
5: {
|
||||
|
||||
@ -173,7 +173,11 @@ class PhotoInfo:
|
||||
"""Returns candidate path for original photo on Photos >= version 5"""
|
||||
if self._info["shared"]:
|
||||
return self._path_5_shared()
|
||||
if self.shared_moment and self._path_shared_moment():
|
||||
if (
|
||||
self.shared_moment
|
||||
and self._db.photos_version >= 7
|
||||
and self._path_shared_moment()
|
||||
):
|
||||
# path for photos in shared moments if it's in the shared moment folder
|
||||
# the file may also be in the originals folder which the next check will catch
|
||||
# check shared_moment first as a photo can be both a shared moment and syndicated
|
||||
@ -222,8 +226,8 @@ class PhotoInfo:
|
||||
)
|
||||
|
||||
def _path_syndication(self):
|
||||
"""Return path for syndicated photo on Photos >= version 8"""
|
||||
# Photos 8+ stores syndicated photos in a separate directory
|
||||
"""Return path for syndicated photo on Photos >= version 7"""
|
||||
# Photos 7+ stores syndicated photos in a separate directory
|
||||
# in ~/Photos Library.photoslibrary/scopes/syndication/originals/X/UUID.ext
|
||||
# where X is first digit of UUID
|
||||
syndication_path = "scopes/syndication/originals"
|
||||
@ -237,8 +241,8 @@ class PhotoInfo:
|
||||
return path if os.path.isfile(path) else None
|
||||
|
||||
def _path_shared_moment(self):
|
||||
"""Return path for shared moment photo on Photos >= version 8"""
|
||||
# Photos 8+ stores shared moment photos in a separate directory
|
||||
"""Return path for shared moment photo on Photos >= version 7"""
|
||||
# Photos 7+ stores shared moment photos in a separate directory
|
||||
# in ~/Photos Library.photoslibrary/scopes/momentshared/originals/X/UUID.ext
|
||||
# where X is first digit of UUID
|
||||
momentshared_path = "scopes/momentshared/originals"
|
||||
@ -371,7 +375,7 @@ class PhotoInfo:
|
||||
)
|
||||
|
||||
def _path_edited_4(self) -> str | None:
|
||||
"""return path_edited for Photos <= 4; modified version of code in PhotoInfo to debug #859"""
|
||||
"""return path_edited for Photos <= 4; #859"""
|
||||
|
||||
if not self._info["hasAdjustments"]:
|
||||
return None
|
||||
@ -476,7 +480,7 @@ class PhotoInfo:
|
||||
|
||||
# In Photos 5, raw is in same folder as original but with _4.ext
|
||||
# Unless "Copy Items to the Photos Library" is not checked
|
||||
# then RAW image is not renamed but has same name is jpeg buth with raw extension
|
||||
# then RAW image is not renamed but has same name is jpeg but with raw extension
|
||||
# Current implementation finds images with the correct raw UTI extension
|
||||
# in same folder as the original and with same stem as original in form: original_stem*.raw_ext
|
||||
# TODO: I don't like this -- would prefer a more deterministic approach but until I have more
|
||||
@ -931,7 +935,7 @@ class PhotoInfo:
|
||||
elif self.live_photo and self.path and not self.ismissing:
|
||||
if self.shared:
|
||||
return self._path_live_photo_shared_5()
|
||||
if self.shared_moment:
|
||||
if self.shared_moment and self._db.photos_version >= 7:
|
||||
return self._path_live_shared_moment()
|
||||
if self.syndicated and not self.saved_to_library:
|
||||
# syndicated ("Shared with you") photos not yet saved to library
|
||||
@ -995,8 +999,8 @@ class PhotoInfo:
|
||||
return photopath
|
||||
|
||||
def _path_live_syndicated(self):
|
||||
"""Return path for live syndicated photo on Photos >= version 8"""
|
||||
# Photos 8+ stores live syndicated photos in a separate directory
|
||||
"""Return path for live syndicated photo on Photos >= version 7"""
|
||||
# Photos 7+ stores live syndicated photos in a separate directory
|
||||
# in ~/Photos Library.photoslibrary/scopes/syndication/originals/X/UUID_3.mov
|
||||
# where X is first digit of UUID
|
||||
syndication_path = "scopes/syndication/originals"
|
||||
@ -1011,8 +1015,8 @@ class PhotoInfo:
|
||||
return live_photo if os.path.isfile(live_photo) else None
|
||||
|
||||
def _path_live_shared_moment(self):
|
||||
"""Return path for live shared moment photo on Photos >= version 8"""
|
||||
# Photos 8+ stores live shared moment photos in a separate directory
|
||||
"""Return path for live shared moment photo on Photos >= version 7"""
|
||||
# Photos 7+ stores live shared moment photos in a separate directory
|
||||
# in ~/Photos Library.photoslibrary/scopes/momentshared/originals/X/UUID_3.mov
|
||||
# where X is first digit of UUID
|
||||
shared_moment_path = "scopes/momentshared/originals"
|
||||
@ -1036,7 +1040,7 @@ class PhotoInfo:
|
||||
return self._path_derivatives_5_shared()
|
||||
|
||||
directory = self._uuid[0] # first char of uuid
|
||||
if self.shared_moment:
|
||||
if self.shared_moment and self._db.photos_version >= 7:
|
||||
# shared moments
|
||||
derivative_path = "scopes/momentshared/resources/derivatives"
|
||||
thumb_path = (
|
||||
@ -1398,9 +1402,9 @@ class PhotoInfo:
|
||||
def syndicated(self) -> bool | None:
|
||||
"""Return true if photo was shared via syndication (e.g. via Messages, etc.);
|
||||
these are photos that appear in "Shared with you" album.
|
||||
Photos 8+ only; returns None if not Photos 8+.
|
||||
Photos 7+ only; returns None if not Photos 7+.
|
||||
"""
|
||||
if self._db.photos_version < 8:
|
||||
if self._db.photos_version < 7:
|
||||
return None
|
||||
|
||||
try:
|
||||
@ -1415,10 +1419,10 @@ class PhotoInfo:
|
||||
def saved_to_library(self) -> bool | None:
|
||||
"""Return True if syndicated photo has been saved to library;
|
||||
returns False if photo is not syndicated or has not been saved to the library.
|
||||
Returns None if not Photos 8+.
|
||||
Syndicated photos are photos that appear in "Shared with you" album; Photos 8+ only.
|
||||
Returns None if not Photos 7+.
|
||||
Syndicated photos are photos that appear in "Shared with you" album; Photos 7+ only.
|
||||
"""
|
||||
if self._db.photos_version < 8:
|
||||
if self._db.photos_version < 7:
|
||||
return None
|
||||
|
||||
try:
|
||||
@ -1428,7 +1432,7 @@ class PhotoInfo:
|
||||
|
||||
@cached_property
|
||||
def shared_moment(self) -> bool:
|
||||
"""Returns True if photo is part of a shared moment otherwise False"""
|
||||
"""Returns True if photo is part of a shared moment otherwise False (Photos 7+ only)"""
|
||||
return bool(self._info["moment_share"])
|
||||
|
||||
@property
|
||||
|
||||
@ -4,4 +4,4 @@ Processes a Photos.app library database to extract information about photos
|
||||
"""
|
||||
|
||||
from .photosdb import PhotosDB
|
||||
from .photosdb_utils import get_db_model_version, get_db_version, get_model_version
|
||||
from .photosdb_utils import get_photos_version_from_model, get_db_version, get_model_version
|
||||
|
||||
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .._constants import _DB_TABLE_NAMES
|
||||
from .._constants import _DB_TABLE_NAMES, _PHOTOS_SYNDICATION_MODEL_VERSION
|
||||
from ..sqlite_utils import sqlite_open_ro
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -16,15 +16,18 @@ def _process_syndicationinfo(self: PhotosDB):
|
||||
|
||||
self._db_syndication_uuid = {}
|
||||
|
||||
if self.photos_version < 8:
|
||||
if self.photos_version < 7:
|
||||
raise NotImplementedError(
|
||||
f"syndication info not implemented for this database version: {self.photos_version}"
|
||||
)
|
||||
else:
|
||||
_process_syndicationinfo_8(self)
|
||||
|
||||
if self._model_ver < _PHOTOS_SYNDICATION_MODEL_VERSION:
|
||||
return
|
||||
|
||||
_process_syndicationinfo_7(self)
|
||||
|
||||
|
||||
def _process_syndicationinfo_8(photosdb: PhotosDB):
|
||||
def _process_syndicationinfo_7(photosdb: PhotosDB):
|
||||
"""Process Syndication info for Photos 8.0 and later
|
||||
|
||||
Args:
|
||||
|
||||
@ -62,7 +62,7 @@ from ..rich_utils import add_rich_markup_tag
|
||||
from ..sqlite_utils import sqlite_db_is_locked, sqlite_open_ro
|
||||
from ..unicode import normalize_unicode
|
||||
from ..utils import _check_file_exists, get_last_library_path, noop
|
||||
from .photosdb_utils import get_db_model_version, get_db_version
|
||||
from .photosdb_utils import get_photos_version_from_model, get_db_version, get_model_version
|
||||
|
||||
if is_macos:
|
||||
import photoscript
|
||||
@ -326,7 +326,7 @@ class PhotosDB:
|
||||
# photoanalysisd sometimes maintains this lock even after Photos is closed
|
||||
# In those cases, make a temp copy of the file for sqlite3 to read
|
||||
if sqlite_db_is_locked(self._dbfile):
|
||||
verbose(f"Database locked, creating temporary copy.")
|
||||
verbose("Database locked, creating temporary copy.")
|
||||
self._tmp_db = self._copy_db_file(self._dbfile)
|
||||
|
||||
# _db_version is set from photos.db
|
||||
@ -341,21 +341,23 @@ class PhotosDB:
|
||||
self._photos_ver = 4
|
||||
else:
|
||||
self._photos_ver = 5
|
||||
self._model_ver = 0 # only set for Photos 5+
|
||||
|
||||
# If Photos >= 5, actual data isn't in photos.db but in Photos.sqlite
|
||||
if int(self._db_version) > int(_PHOTOS_4_VERSION):
|
||||
dbpath = pathlib.Path(self._dbfile).parent
|
||||
dbfile = dbpath / "Photos.sqlite"
|
||||
if not _check_file_exists(dbfile):
|
||||
raise FileNotFoundError(f"dbfile {dbfile} does not exist", dbfile)
|
||||
else:
|
||||
self._dbfile_actual = self._tmp_db = dbfile
|
||||
verbose(f"Processing database {self._filepath(self._dbfile_actual)}")
|
||||
# if database is exclusively locked, make a copy of it and use the copy
|
||||
if sqlite_db_is_locked(self._dbfile_actual):
|
||||
verbose(f"Database locked, creating temporary copy.")
|
||||
self._tmp_db = self._copy_db_file(self._dbfile_actual)
|
||||
self._dbfile_actual = self._tmp_db = dbfile
|
||||
verbose(f"Processing database {self._filepath(self._dbfile_actual)}")
|
||||
# if database is exclusively locked, make a copy of it and use the copy
|
||||
if sqlite_db_is_locked(self._dbfile_actual):
|
||||
verbose("Database locked, creating temporary copy.")
|
||||
self._tmp_db = self._copy_db_file(self._dbfile_actual)
|
||||
# set the photos version to actual value based on Photos.sqlite
|
||||
self._photos_ver = get_db_model_version(self._tmp_db)
|
||||
self._photos_ver = get_photos_version_from_model(self._tmp_db)
|
||||
self._model_ver = get_model_version(self._tmp_db)
|
||||
|
||||
logger.debug(
|
||||
f"_dbfile = {self._dbfile}, _dbfile_actual = {self._dbfile_actual}"
|
||||
@ -1235,7 +1237,7 @@ class PhotosDB:
|
||||
# photos 5+ only, for shared photos
|
||||
self._dbphotos[uuid]["cloudownerhashedpersonid"] = None
|
||||
|
||||
# photos 8+ only, shared moments
|
||||
# photos 7+ only, shared moments
|
||||
self._dbphotos[uuid]["moment_share"] = None
|
||||
|
||||
# compute signatures for finding possible duplicates
|
||||
@ -2004,7 +2006,7 @@ class PhotosDB:
|
||||
# 41 ZGENERICASSET.ZADDEDDATE -- date item added to the library
|
||||
# 42 ZGENERICASSET.Z_PK -- primary key
|
||||
# 43 ZGENERICASSET.ZCLOUDOWNERHASHEDPERSONID -- used to look up owner name (for shared photos)
|
||||
# 44 ZASSET.ZMOMENTSHARE -- FK for ZSHARE (shared moments, Photos 8+)
|
||||
# 44 ZASSET.ZMOMENTSHARE -- FK for ZSHARE (shared moments, Photos 5+; in Photos 7+ these are in the scopes/momentshared folder)
|
||||
|
||||
for row in c:
|
||||
uuid = row[0]
|
||||
@ -2526,7 +2528,7 @@ class PhotosDB:
|
||||
verbose("Processing moments.")
|
||||
self._process_moments()
|
||||
|
||||
if self.photos_version >= 8:
|
||||
if self.photos_version >= 7:
|
||||
verbose("Processing syndication info.")
|
||||
self._process_syndicationinfo()
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@ from ..sqlite_utils import sqlite_open_ro
|
||||
__all__ = [
|
||||
"get_db_version",
|
||||
"get_model_version",
|
||||
"get_db_model_version",
|
||||
"get_photos_version_from_model",
|
||||
"get_photos_library_version",
|
||||
]
|
||||
|
||||
@ -94,7 +94,7 @@ def get_model_version(db_file: str) -> str:
|
||||
return plist["PLModelVersion"]
|
||||
|
||||
|
||||
def get_db_model_version(db_file: str) -> int:
|
||||
def get_photos_version_from_model(db_file: str) -> int:
|
||||
"""Returns Photos version based on model version found in db_file
|
||||
|
||||
Args:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user