diff --git a/osxphotos/_version.py b/osxphotos/_version.py index 7c59e29b..8bd98182 100644 --- a/osxphotos/_version.py +++ b/osxphotos/_version.py @@ -1,3 +1,3 @@ """ version info """ -__version__ = "0.42.51" +__version__ = "0.42.52" diff --git a/osxphotos/photoinfo/photoinfo.py b/osxphotos/photoinfo/photoinfo.py index fe0ca47e..0e2966a6 100644 --- a/osxphotos/photoinfo/photoinfo.py +++ b/osxphotos/photoinfo/photoinfo.py @@ -149,41 +149,11 @@ class PhotoInfo: except AttributeError: self._path = None photopath = None - # TODO: should path try to return path even if ismissing? if self._info["isMissing"] == 1: return photopath # path would be meaningless until downloaded if self._db._db_version <= _PHOTOS_4_VERSION: - if self._info["has_raw"]: - # return the path to JPEG even if RAW is original - vol = ( - self._db._dbvolumes[self._info["raw_pair_info"]["volumeId"]] - if self._info["raw_pair_info"]["volumeId"] is not None - else None - ) - if vol is not None: - photopath = os.path.join( - "/Volumes", vol, self._info["raw_pair_info"]["imagePath"] - ) - else: - photopath = os.path.join( - self._db._masters_path, - self._info["raw_pair_info"]["imagePath"], - ) - else: - vol = self._info["volume"] - if vol is not None: - photopath = os.path.join( - "/Volumes", vol, self._info["imagePath"] - ) - else: - photopath = os.path.join( - self._db._masters_path, self._info["imagePath"] - ) - if not os.path.isfile(photopath): - photopath = None - self._path = photopath - return photopath + return self._path_4() if self._info["shared"]: # shared photo @@ -213,6 +183,37 @@ class PhotoInfo: self._path = photopath return photopath + def _path_4(self): + """return path for photo on Photos <= version 4""" + if self._info["has_raw"]: + # return the path to JPEG even if RAW is original + vol = ( + self._db._dbvolumes[self._info["raw_pair_info"]["volumeId"]] + if self._info["raw_pair_info"]["volumeId"] is not None + else None + ) + if vol is not None: + photopath = os.path.join( + "/Volumes", vol, self._info["raw_pair_info"]["imagePath"] + ) + else: + photopath = os.path.join( + self._db._masters_path, + self._info["raw_pair_info"]["imagePath"], + ) + else: + vol = self._info["volume"] + if vol is not None: + photopath = os.path.join("/Volumes", vol, self._info["imagePath"]) + else: + photopath = os.path.join( + self._db._masters_path, self._info["imagePath"] + ) + if not os.path.isfile(photopath): + photopath = None + self._path = photopath + return photopath + @property def path_edited(self): """absolute path on disk of the edited picture""" @@ -252,14 +253,10 @@ class PhotoInfo: filename = None if self._info["type"] == _PHOTO_TYPE: # it's a photo - if self._db._photos_ver == 5: - filename = f"{self._uuid}_1_201_a.jpeg" + if self._db._photos_ver != 5 and self.uti == "public.heic": + filename = f"{self._uuid}_1_201_a.heic" else: - # could be a heic or a jpeg - if self.uti == "public.heic": - filename = f"{self._uuid}_1_201_a.heic" - else: - filename = f"{self._uuid}_1_201_a.jpeg" + filename = f"{self._uuid}_1_201_a.jpeg" elif self._info["type"] == _MOVIE_TYPE: # it's a movie filename = f"{self._uuid}_2_0_a.mov" @@ -374,21 +371,9 @@ class PhotoInfo: # return photopath if self._db._db_version <= _PHOTOS_4_VERSION: - vol = self._info["raw_info"]["volume"] - if vol is not None: - photopath = os.path.join( - "/Volumes", vol, self._info["raw_info"]["imagePath"] - ) - else: - photopath = os.path.join( - self._db._masters_path, self._info["raw_info"]["imagePath"] - ) - if not os.path.isfile(photopath): - logging.debug( - f"MISSING PATH: RAW photo for UUID {self._uuid} should be at {photopath} but does not appear to exist" - ) - photopath = None - else: + return self._path_raw_4() + + if not self.isreference: filestem = pathlib.Path(self._info["filename"]).stem # raw_ext = get_preferred_uti_extension(self._info["UTI_raw"]) @@ -405,12 +390,40 @@ class PhotoInfo: if not raw_file: photopath = None else: - photopath = os.path.join(filepath, raw_file[0]) - if not os.path.isfile(photopath): - photopath = None + photopath = pathlib.Path(filepath) / raw_file[0] + photopath = str(photopath) if photopath.is_file() else None + else: + # is a reference + try: + photopath = ( + pathlib.Path("/Volumes") + / self._info["raw_volume"] + / self._info["raw_relative_path"] + ) + photopath = str(photopath) if photopath.is_file() else None + except KeyError: + # don't have the path details + photopath = None return photopath + def _path_raw_4(self): + """Return path_raw for Photos <= version 4""" + vol = self._info["raw_info"]["volume"] + if vol is not None: + photopath = os.path.join( + "/Volumes", vol, self._info["raw_info"]["imagePath"] + ) + else: + photopath = os.path.join( + self._db._masters_path, self._info["raw_info"]["imagePath"] + ) + if not os.path.isfile(photopath): + logging.debug( + f"MISSING PATH: RAW photo for UUID {self._uuid} should be at {photopath} but does not appear to exist" + ) + photopath = None + @property def description(self): """long / extended description of picture""" diff --git a/osxphotos/photosdb/photosdb.py b/osxphotos/photosdb/photosdb.py index bc529f56..bd788689 100644 --- a/osxphotos/photosdb/photosdb.py +++ b/osxphotos/photosdb/photosdb.py @@ -246,6 +246,9 @@ class PhotosDB: # key is tuple of (original_filesize, date) and value is list of uuids that match that signature self._db_signatures = {} + # Dict to hold information on volume names (Photos 5+) + self._db_filesystem_volumes = {} + if _debug(): logging.debug(f"dbfile = {dbfile}") @@ -599,9 +602,9 @@ class PhotosDB: verbose = self._verbose verbose("Processing database.") verbose(f"Database version: {self._db_version}.") - - self._photos_ver = 4 # only used in Photos 5+ - + + self._photos_ver = 4 # only used in Photos 5+ + (conn, c) = _open_sql_file(self._tmp_db) # get info to associate persons with photos @@ -2348,7 +2351,8 @@ class PhotosDB: ZINTERNALRESOURCE.ZDATALENGTH, null, ZINTERNALRESOURCE.ZDATASTORESUBTYPE, - ZINTERNALRESOURCE.ZRESOURCETYPE + ZINTERNALRESOURCE.ZRESOURCETYPE, + ZINTERNALRESOURCE.ZFILESYSTEMBOOKMARK FROM {asset_table} JOIN ZINTERNALRESOURCE ON ZINTERNALRESOURCE.ZASSET = ZADDITIONALASSETATTRIBUTES.ZASSET JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK @@ -2360,14 +2364,15 @@ class PhotosDB: ZINTERNALRESOURCE.ZDATALENGTH, ZUNIFORMTYPEIDENTIFIER.ZIDENTIFIER, ZINTERNALRESOURCE.ZDATASTORESUBTYPE, - ZINTERNALRESOURCE.ZRESOURCETYPE + ZINTERNALRESOURCE.ZRESOURCETYPE, + ZINTERNALRESOURCE.ZFILESYSTEMBOOKMARK FROM {asset_table} JOIN ZINTERNALRESOURCE ON ZINTERNALRESOURCE.ZASSET = ZADDITIONALASSETATTRIBUTES.ZASSET JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK JOIN ZUNIFORMTYPEIDENTIFIER ON ZUNIFORMTYPEIDENTIFIER.Z_PK = ZINTERNALRESOURCE.ZUNIFORMTYPEIDENTIFIER WHERE ZINTERNALRESOURCE.ZDATASTORESUBTYPE = 17 """ - + c.execute(sql_raw) for row in c: @@ -2378,6 +2383,33 @@ class PhotosDB: self._dbphotos[uuid]["UTI_raw"] = row[2] self._dbphotos[uuid]["datastore_subtype"] = row[3] self._dbphotos[uuid]["resource_type"] = row[4] + self._dbphotos[uuid]["raw_bookmark"] = row[5] + + # get paths for the relative imports for RAW+JPEG images + c.execute( + f""" SELECT + {asset_table}.ZUUID, + ZFILESYSTEMVOLUME.ZNAME, + ZFILESYSTEMBOOKMARK.ZPATHRELATIVETOVOLUME + FROM {asset_table} + JOIN ZINTERNALRESOURCE ON ZINTERNALRESOURCE.ZASSET = ZADDITIONALASSETATTRIBUTES.ZASSET + JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK + JOIN ZFILESYSTEMBOOKMARK ON ZFILESYSTEMBOOKMARK.ZRESOURCE = ZINTERNALRESOURCE.Z_PK + JOIN ZFILESYSTEMVOLUME ON ZFILESYSTEMVOLUME.Z_PK = ZINTERNALRESOURCE.ZFILESYSTEMVOLUME + WHERE ZINTERNALRESOURCE.ZDATASTORESUBTYPE = 17 + """ + ) + + # path to the raw image will be /Volumes/ZFILESYSTEMVOLUME.ZNAME/ZFILESYSTEMBOOKMARK.ZPATHRELATIVETOVOLUME + # 0: {asset_table}.ZUUID, -- UUID + # 1: ZFILESYSTEMVOLUME.ZNAME, -- name of the volume + # 2: ZFILESYSTEMBOOKMARK.ZPATHRELATIVETOVOLUME -- path to the raw image + + for row in c: + uuid = row[0] + if uuid in self._dbphotos: + self._dbphotos[uuid]["raw_volume"] = row[1] + self._dbphotos[uuid]["raw_relative_path"] = row[2] # add faces and keywords to photo data for uuid in self._dbphotos: