From 1a89a18a011a25616d7a18fb9bf1270b0b206fb4 Mon Sep 17 00:00:00 2001 From: Rhet Turnbull Date: Thu, 2 Jan 2020 09:26:44 -0800 Subject: [PATCH] Initial support for live photos (Photos 5 only) --- osxphotos/photoinfo.py | 38 +++++++++++++++++++++++++++++++++++++- osxphotos/photosdb.py | 9 ++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/osxphotos/photoinfo.py b/osxphotos/photoinfo.py index d80cc6e8..bf63adea 100644 --- a/osxphotos/photoinfo.py +++ b/osxphotos/photoinfo.py @@ -114,7 +114,8 @@ class PhotoInfo: def path_edited(self): """ absolute path on disk of the edited picture """ """ None if photo has not been edited """ - photopath = "" + + photopath = None if self._db._db_version < _PHOTOS_5_VERSION: if self._info["hasAdjustments"]: @@ -329,6 +330,41 @@ class PhotoInfo: else: return [] + @property + def live_photo(self): + """ Returns True if photo is a live photo, otherwise False """ + # TODO: fixme for Photos 4 + if self._db._db_version >= _PHOTOS_5_VERSION: + return self._info["live_photo"] + else: + return None + + @property + def path_live_photo(self): + """ Returns path to the associated video file for a live photo + If photo is not a live photo, returns None + If photo is missing, returns None """ + + photopath = None + # TODO: fixme for Photos 4 + if self._db._db_version < _PHOTOS_5_VERSION: + photopath = None + else: + if self.live_photo and not self.ismissing: + filename = pathlib.Path(self.path) + photopath = filename.parent.joinpath(f"{filename.stem}_3.mov") + if not os.path.isfile(photopath): + photopath = None + # In testing, I've seen occasional missing movie for live photo + # TODO: should this be a warning or debug? + logging.debug( + f"live photo path for UUID {self._uuid} should be at {photopath} but does not appear to exist" + ) + else: + photopath = None + + return photopath + def export( self, dest, diff --git a/osxphotos/photosdb.py b/osxphotos/photosdb.py index 9ff8277f..fa423455 100644 --- a/osxphotos/photosdb.py +++ b/osxphotos/photosdb.py @@ -851,7 +851,8 @@ class PhotosDB: ZGENERICASSET.ZKIND, ZGENERICASSET.ZUNIFORMTYPEIDENTIFIER, ZGENERICASSET.ZAVALANCHEUUID, - ZGENERICASSET.ZAVALANCHEPICKTYPE + ZGENERICASSET.ZAVALANCHEPICKTYPE, + ZGENERICASSET.ZKINDSUBTYPE FROM ZGENERICASSET JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = ZGENERICASSET.Z_PK WHERE ZGENERICASSET.ZTRASHEDSTATE = 0 @@ -879,6 +880,8 @@ class PhotosDB: # 18 ZUNIFORMTYPEIDENTIFIER -- UTI # 19 ZGENERICASSET.ZAVALANCHEUUID, -- if not NULL, is burst photo # 20 ZGENERICASSET.ZAVALANCHEPICKTYPE -- if not 2, is a selected burst photo + # 21 ZGENERICASSET.ZKINDSUBTYPE + for row in c: uuid = row[0] @@ -957,6 +960,10 @@ class PhotosDB: info["burst"] = False info["burst_key"] = None + # Info on sub-type (live photo, panorama, etc) + info["subtype"] = row[21] + info["live_photo"] = True if row[21] == 2 else False + self._dbphotos[uuid] = info # # if row[19] is not None and ((row[20] == 2) or (row[20] == 4)):