Added support for burst photos; added export-bursts to CLI
This commit is contained in:
@@ -256,15 +256,17 @@ def list_libraries(cli_obj):
|
||||
is_flag=True,
|
||||
help="Search for photos not in shared iCloud album (Photos 5 only).",
|
||||
)
|
||||
@click.option("--burst", is_flag=True, help="Search for photos that were taken in a burst.")
|
||||
@click.option("--not-burst", is_flag=True, help="Search for photos that are not part of a burst.")
|
||||
@click.option(
|
||||
"--only-movies",
|
||||
is_flag=True,
|
||||
help="Search only for movies (default searches both images and movies)",
|
||||
help="Search only for movies (default searches both images and movies).",
|
||||
)
|
||||
@click.option(
|
||||
"--only-photos",
|
||||
is_flag=True,
|
||||
help="Search only for photos/images (default searches both images and movies)",
|
||||
help="Search only for photos/images (default searches both images and movies).",
|
||||
)
|
||||
@click.option(
|
||||
"--json",
|
||||
@@ -301,6 +303,8 @@ def query(
|
||||
only_movies,
|
||||
only_photos,
|
||||
uti,
|
||||
burst,
|
||||
not_burst,
|
||||
):
|
||||
""" Query the Photos database using 1 or more search options;
|
||||
if more than one option is provided, they are treated as "AND"
|
||||
@@ -331,6 +335,8 @@ def query(
|
||||
only_movies,
|
||||
only_photos,
|
||||
uti,
|
||||
burst,
|
||||
not_burst,
|
||||
]
|
||||
):
|
||||
click.echo(cli.commands["query"].get_help(ctx))
|
||||
@@ -359,6 +365,10 @@ def query(
|
||||
# can't have only photos and only movies
|
||||
click.echo(cli.commands["query"].get_help(ctx))
|
||||
return
|
||||
elif burst and not_burst:
|
||||
# can't search for both burst and not_burst
|
||||
click.echo(cli.commands["query"].get_help(ctx))
|
||||
return
|
||||
|
||||
# actually have something to query
|
||||
isphoto = ismovie = True # default searches for everything
|
||||
@@ -392,6 +402,8 @@ def query(
|
||||
isphoto,
|
||||
ismovie,
|
||||
uti,
|
||||
burst,
|
||||
not_burst
|
||||
)
|
||||
print_photo_info(photos, cli_obj.json or json)
|
||||
|
||||
@@ -436,6 +448,8 @@ def query(
|
||||
)
|
||||
@click.option("--hidden", is_flag=True, help="Search for photos marked hidden.")
|
||||
@click.option("--not-hidden", is_flag=True, help="Search for photos not marked hidden.")
|
||||
@click.option("--burst", is_flag=True, help="Search for photos that were taken in a burst.")
|
||||
@click.option("--not-burst", is_flag=True, help="Search for photos that are not part of a burst.")
|
||||
@click.option(
|
||||
"--shared",
|
||||
is_flag=True,
|
||||
@@ -467,6 +481,11 @@ def query(
|
||||
help="Also export edited version of photo "
|
||||
'if an edited version exists. Edited photo will be named in form of "photoname_edited.ext"',
|
||||
)
|
||||
@click.option(
|
||||
"--export-bursts",
|
||||
is_flag=True,
|
||||
help="If a photo is a burst photo export all associated burst images in the library."
|
||||
)
|
||||
@click.option(
|
||||
"--original-name",
|
||||
is_flag=True,
|
||||
@@ -484,12 +503,12 @@ def query(
|
||||
@click.option(
|
||||
"--only-movies",
|
||||
is_flag=True,
|
||||
help="Search only for movies (default searches both images and movies)",
|
||||
help="Search only for movies (default searches both images and movies).",
|
||||
)
|
||||
@click.option(
|
||||
"--only-photos",
|
||||
is_flag=True,
|
||||
help="Search only for photos/images (default searches both images and movies)",
|
||||
help="Search only for photos/images (default searches both images and movies).",
|
||||
)
|
||||
@click.argument("dest", nargs=1)
|
||||
@click.pass_obj
|
||||
@@ -519,10 +538,13 @@ def export(
|
||||
overwrite,
|
||||
export_by_date,
|
||||
export_edited,
|
||||
export_bursts,
|
||||
original_name,
|
||||
sidecar,
|
||||
only_photos,
|
||||
only_movies,
|
||||
burst,
|
||||
not_burst,
|
||||
dest,
|
||||
):
|
||||
""" Export photos from the Photos database.
|
||||
@@ -559,6 +581,10 @@ def export(
|
||||
# can't have only photos and only movies
|
||||
click.echo(cli.commands["export"].get_help(ctx))
|
||||
return
|
||||
elif burst and not_burst:
|
||||
# can't search for both burst and not_burst
|
||||
click.echo(cli.commands["export"].get_help(ctx))
|
||||
return
|
||||
|
||||
isphoto = ismovie = True # default searches for everything
|
||||
if only_movies:
|
||||
@@ -591,9 +617,18 @@ def export(
|
||||
isphoto,
|
||||
ismovie,
|
||||
uti,
|
||||
burst,
|
||||
not_burst,
|
||||
)
|
||||
|
||||
if photos:
|
||||
if export_bursts:
|
||||
# add the burst_photos to the export set
|
||||
photos_burst = [p for p in photos if p.burst]
|
||||
for burst in photos_burst:
|
||||
burst_set = [p for p in burst.burst_photos if not p.ismissing]
|
||||
photos.extend(burst_set)
|
||||
|
||||
num_photos = len(photos)
|
||||
photo_str = "photos" if num_photos > 1 else "photo"
|
||||
click.echo(f"Exporting {num_photos} {photo_str} to {dest}...")
|
||||
@@ -679,6 +714,7 @@ def print_photo_info(photos, json=False):
|
||||
"isphoto",
|
||||
"ismovie",
|
||||
"uti",
|
||||
"burst",
|
||||
]
|
||||
)
|
||||
for p in photos:
|
||||
@@ -706,6 +742,7 @@ def print_photo_info(photos, json=False):
|
||||
p.isphoto,
|
||||
p.ismovie,
|
||||
p.uti,
|
||||
p.burst,
|
||||
]
|
||||
)
|
||||
for row in dump:
|
||||
@@ -737,6 +774,8 @@ def _query(
|
||||
isphoto,
|
||||
ismovie,
|
||||
uti,
|
||||
burst,
|
||||
not_burst,
|
||||
):
|
||||
""" run a query against PhotosDB to extract the photos based on user supply criteria """
|
||||
""" used by query and export commands """
|
||||
@@ -817,6 +856,11 @@ def _query(
|
||||
if uti:
|
||||
photos = [p for p in photos if uti in p.uti]
|
||||
|
||||
if burst:
|
||||
photos = [p for p in photos if p.burst]
|
||||
elif not_burst:
|
||||
photos = [p for p in photos if not p.burst]
|
||||
|
||||
return photos
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.19.04"
|
||||
__version__ = "0.20.00"
|
||||
|
||||
@@ -311,7 +311,6 @@ class PhotoInfo:
|
||||
@property
|
||||
def burst(self):
|
||||
""" Returns True if photo is part of a Burst photo set, otherwise False """
|
||||
# TODO: update for Photos 4
|
||||
return self._info["burst"]
|
||||
|
||||
@property
|
||||
@@ -320,7 +319,7 @@ class PhotoInfo:
|
||||
that are part of the same burst photo set; otherwise returns empty list.
|
||||
self is not included in the returned list """
|
||||
if self._info["burst"]:
|
||||
burst_uuid = self._info["avalancheUUID"]
|
||||
burst_uuid = self._info["burstUUID"]
|
||||
burst_photos = [
|
||||
PhotoInfo(db=self._db, uuid=u, info=self._db._dbphotos[u])
|
||||
for u in self._db._dbphotos_burst[burst_uuid]
|
||||
@@ -561,6 +560,7 @@ class PhotoInfo:
|
||||
"isphoto": self.isphoto,
|
||||
"ismovie": self.ismovie,
|
||||
"uti": self.uti,
|
||||
"burst": self.burst,
|
||||
}
|
||||
return yaml.dump(info, sort_keys=False)
|
||||
|
||||
@@ -589,6 +589,7 @@ class PhotoInfo:
|
||||
"isphoto": self.isphoto,
|
||||
"ismovie": self.ismovie,
|
||||
"uti": self.uti,
|
||||
"burst": self.burst,
|
||||
}
|
||||
return json.dumps(pic)
|
||||
|
||||
|
||||
@@ -477,15 +477,16 @@ class PhotosDB:
|
||||
|
||||
# Get photo details
|
||||
c.execute(
|
||||
"select RKVersion.uuid, RKVersion.modelId, RKVersion.masterUuid, RKVersion.filename, "
|
||||
+ "RKVersion.lastmodifieddate, RKVersion.imageDate, RKVersion.mainRating, "
|
||||
+ "RKVersion.hasAdjustments, RKVersion.hasKeywords, RKVersion.imageTimeZoneOffsetSeconds, "
|
||||
+ "RKMaster.volumeId, RKMaster.imagePath, RKVersion.extendedDescription, RKVersion.name, "
|
||||
+ "RKMaster.isMissing, RKMaster.originalFileName, RKVersion.isFavorite, RKVersion.isHidden, "
|
||||
+ "RKVersion.latitude, RKVersion.longitude, "
|
||||
+ "RKVersion.adjustmentUuid, RKVersion.type, RKMaster.UTI "
|
||||
+ "from RKVersion, RKMaster where RKVersion.isInTrash = 0 and "
|
||||
+ "RKVersion.masterUuid = RKMaster.uuid and RKVersion.filename not like '%.pdf'"
|
||||
""" SELECT RKVersion.uuid, RKVersion.modelId, RKVersion.masterUuid, RKVersion.filename,
|
||||
RKVersion.lastmodifieddate, RKVersion.imageDate, RKVersion.mainRating,
|
||||
RKVersion.hasAdjustments, RKVersion.hasKeywords, RKVersion.imageTimeZoneOffsetSeconds,
|
||||
RKMaster.volumeId, RKMaster.imagePath, RKVersion.extendedDescription, RKVersion.name,
|
||||
RKMaster.isMissing, RKMaster.originalFileName, RKVersion.isFavorite, RKVersion.isHidden,
|
||||
RKVersion.latitude, RKVersion.longitude,
|
||||
RKVersion.adjustmentUuid, RKVersion.type, RKMaster.UTI,
|
||||
RKVersion.burstUuid, RKVersion.burstPickType
|
||||
from RKVersion, RKMaster where RKVersion.isInTrash = 0 and
|
||||
RKVersion.masterUuid = RKMaster.uuid and RKVersion.filename not like '%.pdf' """
|
||||
)
|
||||
|
||||
# order of results
|
||||
@@ -512,17 +513,14 @@ class PhotosDB:
|
||||
# 20 RKVersion.adjustmentUuid
|
||||
# 21 RKVersion.type
|
||||
# 22 RKMaster.UTI
|
||||
# 23 RKVersion.burstUuid
|
||||
# 24 RKVersion.burstPickType
|
||||
|
||||
for row in c:
|
||||
uuid = row[0]
|
||||
if _debug():
|
||||
logging.debug(f"uuid = '{uuid}, master = '{row[2]}")
|
||||
self._dbphotos[uuid] = {}
|
||||
|
||||
# temp fix for burst until burst photos implemented for photos 4
|
||||
# TODO: fixme
|
||||
self._dbphotos[uuid]["burst"] = self._dbphotos[uuid]["burst_key"] = None
|
||||
|
||||
self._dbphotos[uuid]["_uuid"] = uuid # stored here for easier debugging
|
||||
self._dbphotos[uuid]["modelID"] = row[1]
|
||||
self._dbphotos[uuid]["masterUuid"] = row[2]
|
||||
@@ -557,7 +555,7 @@ class PhotosDB:
|
||||
self._dbphotos[uuid]["adjustmentUuid"] = row[20]
|
||||
self._dbphotos[uuid]["adjustmentFormatID"] = None
|
||||
|
||||
# find type
|
||||
# find type and UTI
|
||||
if row[21] == 2:
|
||||
# photo
|
||||
self._dbphotos[uuid]["type"] = _PHOTO_TYPE
|
||||
@@ -572,6 +570,26 @@ class PhotosDB:
|
||||
|
||||
self._dbphotos[uuid]["UTI"] = row[22]
|
||||
|
||||
# handle burst photos
|
||||
# if burst photo, determine whether or not it's a selected burst photo
|
||||
self._dbphotos[uuid]["burstUUID"] = row[23]
|
||||
self._dbphotos[uuid]["burstPickType"] = row[24]
|
||||
if row[23] is not None:
|
||||
# it's a burst photo
|
||||
self._dbphotos[uuid]["burst"] = True
|
||||
burst_uuid = row[23]
|
||||
if burst_uuid not in self._dbphotos_burst:
|
||||
self._dbphotos_burst[burst_uuid] = set()
|
||||
self._dbphotos_burst[burst_uuid].add(uuid)
|
||||
if row[24] != 2 and row[24] != 4:
|
||||
self._dbphotos[uuid]["burst_key"] = True # it's a key photo (selected from the burst)
|
||||
else:
|
||||
self._dbphotos[uuid]["burst_key"] = False # it's a burst photo but not one that's selected
|
||||
else:
|
||||
# not a burst photo
|
||||
self._dbphotos[uuid]["burst"] = False
|
||||
self._dbphotos[uuid]["burst_key"] = None
|
||||
|
||||
# get details needed to find path of the edited photos and live photos
|
||||
c.execute(
|
||||
"SELECT RKVersion.uuid, RKVersion.adjustmentUuid, RKModelResource.modelId, "
|
||||
@@ -917,11 +935,12 @@ class PhotosDB:
|
||||
info["type"] = None
|
||||
|
||||
info["UTI"] = row[18]
|
||||
info["avalancheUUID"] = row[19]
|
||||
info["avalanchePickType"] = row[20]
|
||||
|
||||
# handle burst photos
|
||||
# if burst photo, determine whether or not it's a selected burst photo
|
||||
# in Photos 5, burstUUID is called avalancheUUID
|
||||
info["burstUUID"] = row[19] # avalancheUUID
|
||||
info["burstPickType"] = row[20] #avalanchePickType
|
||||
if row[19] is not None:
|
||||
# it's a burst photo
|
||||
info["burst"] = True
|
||||
|
||||
Reference in New Issue
Block a user