Bug shared moments 1116 (#1119)
* Partial for #1116, shared moment photos * Added --shared-moment, --not-shared-moment query args
This commit is contained in:
parent
4f0e2a101d
commit
052f2791ac
@ -1188,6 +1188,10 @@ Return true if photo was shared via syndication (e.g. via Messages, etc.); these
|
||||
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+.
|
||||
|
||||
### `shared_moment`
|
||||
|
||||
Return True if photo is part of a shared moment, otherwise False. Shared moments are created when multiple photos are shared via iCloud. (e.g. in Messages)
|
||||
|
||||
#### `uti`
|
||||
|
||||
Returns Uniform Type Identifier (UTI) for the current version of the image, for example: 'public.jpeg' or 'com.apple. quicktime-movie'. If the image has been edited, `uti` will return the UTI for the edited image, otherwise it will return the UTI for the original image.
|
||||
|
||||
@ -611,6 +611,16 @@ _QUERY_PARAMETERS_DICT = {
|
||||
is_flag=True,
|
||||
help="Search for syndicated photos that have not saved to the library",
|
||||
),
|
||||
"--shared-moment": click.Option(
|
||||
["--shared-moment"],
|
||||
is_flag=True,
|
||||
help="Search for photos that are part of a shared moment",
|
||||
),
|
||||
"--not-shared-moment": click.Option(
|
||||
["--not-shared-moment"],
|
||||
is_flag=True,
|
||||
help="Search for photos that are not part of a shared moment",
|
||||
),
|
||||
"--regex": click.Option(
|
||||
["--regex"],
|
||||
metavar="REGEX TEMPLATE",
|
||||
|
||||
@ -894,6 +894,8 @@ def export(
|
||||
not_syndicated,
|
||||
saved_to_library,
|
||||
not_saved_to_library,
|
||||
shared_moment,
|
||||
not_shared_moment,
|
||||
selected=False, # Isn't provided on unsupported platforms
|
||||
# debug, # debug, watch, breakpoint handled in cli/__init__.py
|
||||
# watch,
|
||||
@ -1120,6 +1122,8 @@ def export(
|
||||
not_syndicated = cfg.not_syndicated
|
||||
saved_to_library = cfg.saved_to_library
|
||||
not_saved_to_library = cfg.not_saved_to_library
|
||||
shared_moment = cfg.shared_moment
|
||||
not_shared_moment = cfg.not_shared_moment
|
||||
|
||||
# config file might have changed verbose
|
||||
verbose = verbose_print(verbose=verbose_flag, timestamp=timestamp, theme=theme)
|
||||
@ -1171,6 +1175,7 @@ def export(
|
||||
("title", "no_title"),
|
||||
("syndicated", "not_syndicated"),
|
||||
("saved_to_library", "not_saved_to_library"),
|
||||
("shared_moment", "not_shared_moment"),
|
||||
]
|
||||
dependent_options = [
|
||||
("append", ("report")),
|
||||
|
||||
@ -173,6 +173,12 @@ 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():
|
||||
# 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
|
||||
# and if so, will be in the shared moment folder
|
||||
return self._path_shared_moment()
|
||||
if self.syndicated and not self.saved_to_library:
|
||||
# path for "shared with you" syndicated photos that have not yet been saved to the library
|
||||
return self._path_syndication()
|
||||
@ -230,6 +236,21 @@ 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
|
||||
# in ~/Photos Library.photoslibrary/scopes/momentshared/originals/X/UUID.ext
|
||||
# where X is first digit of UUID
|
||||
momentshared_path = "scopes/momentshared/originals"
|
||||
uuid_dir = self.uuid[0]
|
||||
path = os.path.join(
|
||||
self._db._library_path,
|
||||
momentshared_path,
|
||||
uuid_dir,
|
||||
self.filename,
|
||||
)
|
||||
return path if os.path.isfile(path) else None
|
||||
|
||||
def _path_4(self):
|
||||
"""Returns candidate path for original photo on Photos <= version 4"""
|
||||
if self._info["has_raw"]:
|
||||
@ -910,6 +931,8 @@ 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:
|
||||
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
|
||||
return self._path_live_syndicated()
|
||||
@ -987,6 +1010,22 @@ 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
|
||||
# in ~/Photos Library.photoslibrary/scopes/momentshared/originals/X/UUID_3.mov
|
||||
# where X is first digit of UUID
|
||||
shared_moment_path = "scopes/momentshared/originals"
|
||||
uuid_dir = self.uuid[0]
|
||||
filename = f"{pathlib.Path(self.filename).stem}_3.mov"
|
||||
live_photo = os.path.join(
|
||||
self._db._library_path,
|
||||
shared_moment_path,
|
||||
uuid_dir,
|
||||
filename,
|
||||
)
|
||||
return live_photo if os.path.isfile(live_photo) else None
|
||||
|
||||
@cached_property
|
||||
def path_derivatives(self) -> list[str]:
|
||||
"""Return any derivative (preview) images associated with the photo as a list of paths, sorted by file size (largest first)"""
|
||||
@ -997,19 +1036,29 @@ class PhotoInfo:
|
||||
return self._path_derivatives_5_shared()
|
||||
|
||||
directory = self._uuid[0] # first char of uuid
|
||||
if self.syndicated and not self.saved_to_library:
|
||||
if self.shared_moment:
|
||||
# shared moments
|
||||
derivative_path = "scopes/momentshared/resources/derivatives"
|
||||
thumb_path = (
|
||||
f"{derivative_path}/masters/{directory}/{self.uuid}_4_5005_c.jpeg"
|
||||
)
|
||||
elif self.syndicated and not self.saved_to_library:
|
||||
# syndicated ("Shared with you") photos not yet saved to library
|
||||
derivative_path = "scopes/syndication/resources/derivatives"
|
||||
thumb_path = (
|
||||
f"{derivative_path}/masters/{directory}/{self.uuid}_4_5005_c.jpeg"
|
||||
)
|
||||
else:
|
||||
derivative_path = f"resources/derivatives/{directory}"
|
||||
derivative_path = "resources/derivatives"
|
||||
thumb_path = (
|
||||
f"resources/derivatives/masters/{directory}/{self.uuid}_4_5005_c.jpeg"
|
||||
)
|
||||
|
||||
derivative_path = pathlib.Path(self._db._library_path).joinpath(derivative_path)
|
||||
derivative_path = (
|
||||
pathlib.Path(self._db._library_path)
|
||||
.joinpath(derivative_path)
|
||||
.joinpath(directory)
|
||||
)
|
||||
thumb_path = pathlib.Path(self._db._library_path).joinpath(thumb_path)
|
||||
|
||||
# find all files that start with uuid in derivative path
|
||||
@ -1377,6 +1426,11 @@ class PhotoInfo:
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
@cached_property
|
||||
def shared_moment(self) -> bool:
|
||||
"""Returns True if photo is part of a shared moment otherwise False"""
|
||||
return bool(self._info["moment_share"])
|
||||
|
||||
@property
|
||||
def labels(self):
|
||||
"""returns list of labels applied to photo by Photos image categorization
|
||||
|
||||
@ -1235,6 +1235,9 @@ class PhotosDB:
|
||||
# photos 5+ only, for shared photos
|
||||
self._dbphotos[uuid]["cloudownerhashedpersonid"] = None
|
||||
|
||||
# photos 8+ only, shared moments
|
||||
self._dbphotos[uuid]["moment_share"] = None
|
||||
|
||||
# compute signatures for finding possible duplicates
|
||||
signature = self._duplicate_signature(uuid)
|
||||
try:
|
||||
@ -1949,7 +1952,8 @@ class PhotosDB:
|
||||
{asset_table}.ZSAVEDASSETTYPE,
|
||||
{asset_table}.ZADDEDDATE,
|
||||
{asset_table}.Z_PK,
|
||||
{asset_table}.ZCLOUDOWNERHASHEDPERSONID
|
||||
{asset_table}.ZCLOUDOWNERHASHEDPERSONID,
|
||||
{asset_table}.ZMOMENTSHARE
|
||||
FROM {asset_table}
|
||||
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK
|
||||
ORDER BY {asset_table}.ZUUID """
|
||||
@ -2000,6 +2004,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+)
|
||||
|
||||
for row in c:
|
||||
uuid = row[0]
|
||||
@ -2187,6 +2192,8 @@ class PhotosDB:
|
||||
info["pk"] = row[42]
|
||||
info["cloudownerhashedpersonid"] = row[43]
|
||||
|
||||
info["moment_share"] = row[44]
|
||||
|
||||
# initialize import session info which will be filled in later
|
||||
# not every photo has an import session so initialize all records now
|
||||
info["import_session"] = None
|
||||
@ -3532,6 +3539,11 @@ class PhotosDB:
|
||||
elif options.not_saved_to_library:
|
||||
photos = [p for p in photos if p.syndicated and not p.saved_to_library]
|
||||
|
||||
if options.shared_moment:
|
||||
photos = [p for p in photos if p.shared_moment]
|
||||
elif options.not_shared_moment:
|
||||
photos = [p for p in photos if not p.shared_moment]
|
||||
|
||||
if options.function:
|
||||
for function in options.function:
|
||||
photos = function[0](photos)
|
||||
|
||||
@ -112,6 +112,8 @@ class QueryOptions:
|
||||
not_syndicated: search for photos that have not been shared via syndication ("Shared with You" album via Messages, etc.)
|
||||
saved_to_library: search for syndicated photos that have been saved to the Photos library
|
||||
not_saved_to_library: search for syndicated photos that have not been saved to the Photos library
|
||||
shared_moment: search for photos that have been shared via a shared moment
|
||||
not_shared_moment: search for photos that have not been shared via a shared moment
|
||||
"""
|
||||
|
||||
added_after: Optional[datetime.datetime] = None
|
||||
@ -200,6 +202,8 @@ class QueryOptions:
|
||||
not_syndicated: Optional[bool] = None
|
||||
saved_to_library: Optional[bool] = None
|
||||
not_saved_to_library: Optional[bool] = None
|
||||
shared_moment: Optional[bool] = None
|
||||
not_shared_moment: Optional[bool] = None
|
||||
|
||||
def asdict(self):
|
||||
return asdict(self)
|
||||
@ -271,6 +275,7 @@ def query_options_from_kwargs(**kwargs) -> QueryOptions:
|
||||
("deleted_only", "not_deleted"),
|
||||
("syndicated", "not_syndicated"),
|
||||
("saved_to_library", "not_saved_to_library"),
|
||||
("shared_moment", "not_shared_moment"),
|
||||
]
|
||||
# TODO: add option to validate requiring at least one query arg
|
||||
for arg, not_arg in exclusive:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user