Initial support for RAW photos in Photos 4 to address issue #101
This commit is contained in:
@@ -855,8 +855,14 @@ def query(
|
||||
|
||||
@cli.command(cls=ExportCommand)
|
||||
@DB_OPTION
|
||||
@query_options
|
||||
# @click.option(
|
||||
# "--all",
|
||||
# is_flag=True,
|
||||
# help="Export all versions of photos including "
|
||||
# "edited photos, live photos, burst photos, and RAW photos.",
|
||||
# )
|
||||
@click.option("--verbose", "-V", is_flag=True, help="Print verbose output.")
|
||||
@query_options
|
||||
@click.option(
|
||||
"--overwrite",
|
||||
is_flag=True,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.27.6"
|
||||
__version__ = "0.27.7"
|
||||
|
||||
@@ -253,10 +253,6 @@ class PhotoInfo:
|
||||
# TODO: I don't like this -- would prefer a more deterministic approach but until I have more
|
||||
# data on how Photos stores and retrieves RAW images, this seems to be working
|
||||
|
||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
||||
logging.warning("RAW support not yet implemented for Photos version < 5")
|
||||
return None
|
||||
|
||||
if self._info["isMissing"] == 1:
|
||||
return None # path would be meaningless until downloaded
|
||||
|
||||
@@ -273,26 +269,45 @@ class PhotoInfo:
|
||||
# )
|
||||
# return photopath
|
||||
|
||||
filestem = pathlib.Path(self._info["filename"]).stem
|
||||
raw_ext = get_preferred_uti_extension(self._info["UTI_raw"])
|
||||
|
||||
if self._info["directory"].startswith("/"):
|
||||
filepath = self._info["directory"]
|
||||
else:
|
||||
filepath = os.path.join(self._db._masters_path, self._info["directory"])
|
||||
|
||||
glob_str = f"{filestem}*.{raw_ext}"
|
||||
raw_file = findfiles(glob_str, filepath)
|
||||
if len(raw_file) != 1:
|
||||
logging.warning(f"Error getting path to RAW file: {filepath}/{glob_str}")
|
||||
photopath = None
|
||||
else:
|
||||
photopath = os.path.join(filepath, raw_file[0])
|
||||
if self._db._db_version < _PHOTOS_5_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:
|
||||
filestem = pathlib.Path(self._info["filename"]).stem
|
||||
raw_ext = get_preferred_uti_extension(self._info["UTI_raw"])
|
||||
|
||||
if self._info["directory"].startswith("/"):
|
||||
filepath = self._info["directory"]
|
||||
else:
|
||||
filepath = os.path.join(self._db._masters_path, self._info["directory"])
|
||||
|
||||
glob_str = f"{filestem}*.{raw_ext}"
|
||||
raw_file = findfiles(glob_str, filepath)
|
||||
if len(raw_file) != 1:
|
||||
logging.warning(
|
||||
f"Error getting path to RAW file: {filepath}/{glob_str}"
|
||||
)
|
||||
photopath = None
|
||||
else:
|
||||
photopath = os.path.join(filepath, raw_file[0])
|
||||
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
|
||||
|
||||
return photopath
|
||||
|
||||
@property
|
||||
@@ -588,10 +603,6 @@ class PhotoInfo:
|
||||
@property
|
||||
def has_raw(self):
|
||||
""" returns True if photo has an associated RAW image, otherwise False """
|
||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
||||
logging.warning("RAW support not yet implemented for Photos version < 5")
|
||||
return None
|
||||
|
||||
return self._info["has_raw"]
|
||||
|
||||
@property
|
||||
|
||||
@@ -99,6 +99,11 @@ class PhotosDB:
|
||||
# e.g. {'BD94B7C0-2EB8-43DB-98B4-3B8E9653C255': {'8B386814-CA8A-42AA-BCA8-97C1AA746D8A', '52B95550-DE4A-44DD-9E67-89E979F2E97F'}}
|
||||
self._dbphotos_burst = {}
|
||||
|
||||
# Dict with additional information from RKMaster
|
||||
# key is UUID from RKMaster, value is dict with info related to each master
|
||||
# currently used to get information on RAW images
|
||||
self._dbphotos_master = {}
|
||||
|
||||
# Dict with information about all persons/photos by uuid
|
||||
# key is photo UUID, value is list of face names in that photo
|
||||
# Note: Photos 5 identifies faces even if not given a name
|
||||
@@ -610,7 +615,10 @@ class PhotosDB:
|
||||
RKVersion.latitude, RKVersion.longitude,
|
||||
RKVersion.adjustmentUuid, RKVersion.type, RKMaster.UTI,
|
||||
RKVersion.burstUuid, RKVersion.burstPickType,
|
||||
RKVersion.specialType, RKMaster.modelID, RKVersion.momentUuid
|
||||
RKVersion.specialType, RKMaster.modelID, null, RKVersion.momentUuid,
|
||||
RKVersion.rawMasterUuid,
|
||||
RKVersion.nonRawMasterUuid,
|
||||
RKMaster.alternateMasterUuid
|
||||
FROM RKVersion, RKMaster WHERE RKVersion.isInTrash = 0 AND
|
||||
RKVersion.masterUuid = RKMaster.uuid AND RKVersion.filename NOT LIKE '%.pdf' """
|
||||
)
|
||||
@@ -625,8 +633,11 @@ class PhotosDB:
|
||||
RKVersion.adjustmentUuid, RKVersion.type, RKMaster.UTI,
|
||||
RKVersion.burstUuid, RKVersion.burstPickType,
|
||||
RKVersion.specialType, RKMaster.modelID,
|
||||
RKVersion.selfPortrait,
|
||||
RKVersion.momentUuid
|
||||
RKVersion.selfPortrait,
|
||||
RKVersion.momentUuid,
|
||||
RKVersion.rawMasterUuid,
|
||||
RKVersion.nonRawMasterUuid,
|
||||
RKMaster.alternateMasterUuid
|
||||
FROM RKVersion, RKMaster WHERE RKVersion.isInTrash = 0 AND
|
||||
RKVersion.masterUuid = RKMaster.uuid AND RKVersion.filename NOT LIKE '%.pdf' """
|
||||
)
|
||||
@@ -661,6 +672,9 @@ class PhotosDB:
|
||||
# 26 RKMaster.modelID
|
||||
# 27 RKVersion.selfPortrait -- 1 if selfie, Photos >= 3, not present for Photos < 3
|
||||
# 28 RKVersion.momentID (# 27 for Photos < 3)
|
||||
# 29 RKVersion.rawMasterUuid, -- UUID of RAW master
|
||||
# 30 RKVersion.nonRawMasterUuid, -- UUID of non-RAW master
|
||||
# 31 RKMaster.alternateMasterUuid -- UUID of alternate master (will be RAW master for JPEG and JPEG master for RAW)
|
||||
|
||||
for row in c:
|
||||
uuid = row[0]
|
||||
@@ -746,6 +760,7 @@ class PhotosDB:
|
||||
# 4 == HDR
|
||||
# 5 == live photo
|
||||
# 6 == screenshot
|
||||
# 7 == JPEG/RAW pair
|
||||
# 8 == HDR live photo
|
||||
# 9 = portrait
|
||||
|
||||
@@ -765,12 +780,12 @@ class PhotosDB:
|
||||
self._dbphotos[uuid]["portrait"] = True if row[25] == 9 else False
|
||||
|
||||
# selfies (front facing camera, RKVersion.selfPortrait == 1)
|
||||
if self._db_version >= _PHOTOS_3_VERSION:
|
||||
if row[27] is not None:
|
||||
self._dbphotos[uuid]["selfie"] = True if row[27] == 1 else False
|
||||
self._dbphotos[uuid]["momentID"] = row[28]
|
||||
else:
|
||||
self._dbphotos[uuid]["selfie"] = None
|
||||
self._dbphotos[uuid]["momentID"] = row[27]
|
||||
|
||||
self._dbphotos[uuid]["momentID"] = row[28]
|
||||
|
||||
# Init cloud details that will be filled in later if cloud asset
|
||||
self._dbphotos[uuid]["cloudAssetGUID"] = None # Photos 5
|
||||
@@ -785,12 +800,59 @@ class PhotosDB:
|
||||
self._dbphotos[uuid]["original_resource_choice"] = None
|
||||
|
||||
# associated RAW image info
|
||||
# will be filled in later
|
||||
self._dbphotos[uuid]["has_raw"] = None
|
||||
self._dbphotos[uuid]["has_raw"] = True if row[25] == 7 else False
|
||||
self._dbphotos[uuid]["UTI_raw"] = None
|
||||
self._dbphotos[uuid]["raw_data_length"] = None
|
||||
self._dbphotos[uuid]["resource_type"] = None
|
||||
self._dbphotos[uuid]["datastore_subtype"] = None
|
||||
self._dbphotos[uuid]["raw_info"] = None
|
||||
self._dbphotos[uuid]["resource_type"] = None # Photos 5
|
||||
self._dbphotos[uuid]["datastore_subtype"] = None # Photos 5
|
||||
self._dbphotos[uuid]["raw_master_uuid"] = row[29]
|
||||
self._dbphotos[uuid]["non_raw_master_uuid"] = row[30]
|
||||
self._dbphotos[uuid]["alt_master_uuid"] = row[31]
|
||||
|
||||
# get additional details from RKMaster, needed for RAW processing
|
||||
c.execute(
|
||||
""" SELECT
|
||||
RKMaster.uuid,
|
||||
RKMaster.volumeId,
|
||||
RKMaster.imagePath,
|
||||
RKMaster.isMissing,
|
||||
RKMaster.originalFileName,
|
||||
RKMaster.UTI,
|
||||
RKMaster.modelID,
|
||||
RKMaster.fileSize,
|
||||
RKMaster.isTrulyRaw,
|
||||
RKMaster.alternateMasterUuid
|
||||
FROM RKMaster
|
||||
"""
|
||||
)
|
||||
|
||||
# Order of results:
|
||||
# 0 RKMaster.uuid,
|
||||
# 1 RKMaster.volumeId,
|
||||
# 2 RKMaster.imagePath,
|
||||
# 3 RKMaster.isMissing,
|
||||
# 4 RKMaster.originalFileName,
|
||||
# 5 RKMaster.UTI,
|
||||
# 6 RKMaster.modelID,
|
||||
# 7 RKMaster.fileSize,
|
||||
# 8 RKMaster.isTrulyRaw,
|
||||
# 9 RKMaster.alternateMasterUuid
|
||||
|
||||
for row in c:
|
||||
uuid = row[0]
|
||||
info = {}
|
||||
info["_uuid"] = uuid
|
||||
info["volumeId"] = row[1]
|
||||
info["imagePath"] = row[2]
|
||||
info["isMissing"] = row[3]
|
||||
info["originalFilename"] = row[4]
|
||||
info["UTI"] = row[5]
|
||||
info["modelID"] = row[6]
|
||||
info["fileSize"] = row[7]
|
||||
info["isTrulyRAW"] = row[8]
|
||||
info["alternateMasterUuid"] = row[9]
|
||||
self._dbphotos_master[uuid] = info
|
||||
|
||||
# get details needed to find path of the edited photos
|
||||
c.execute(
|
||||
@@ -979,6 +1041,20 @@ class PhotosDB:
|
||||
else:
|
||||
self._dbalbum_titles[title] = [album_id]
|
||||
|
||||
# add volume name to _dbphotos_master
|
||||
for info in self._dbphotos_master.values():
|
||||
info["volume"] = (
|
||||
self._dbvolumes[info["volumeId"]]
|
||||
if info["volumeId"] is not None
|
||||
else None
|
||||
)
|
||||
|
||||
# add data on RAW images
|
||||
for info in self._dbphotos.values():
|
||||
if info["has_raw"]:
|
||||
raw_uuid = info["raw_master_uuid"]
|
||||
info["raw_info"] = self._dbphotos_master[raw_uuid]
|
||||
|
||||
# done with the database connection
|
||||
conn.close()
|
||||
|
||||
@@ -1423,6 +1499,10 @@ class PhotosDB:
|
||||
info["UTI_raw"] = None
|
||||
info["datastore_subtype"] = None
|
||||
info["resource_type"] = None
|
||||
info["raw_master_uuid"] = None # Photos 4
|
||||
info["non_raw_master_uuid"] = None # Photos 4
|
||||
info["alt_master_uuid"] = None # Photos 4
|
||||
info["raw_info"] = None # Photos 4
|
||||
|
||||
self._dbphotos[uuid] = info
|
||||
|
||||
|
||||
Reference in New Issue
Block a user