Added support for burst photos; added export-bursts to CLI

This commit is contained in:
Rhet Turnbull
2019-12-31 21:14:53 -08:00
parent 2e1a8d2500
commit 593983a099
5 changed files with 149 additions and 29 deletions

View File

@@ -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

View File

@@ -1,3 +1,3 @@
""" version info """
__version__ = "0.19.04"
__version__ = "0.20.00"

View File

@@ -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)

View File

@@ -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