Refactored export2, #485, #486

This commit is contained in:
Rhet Turnbull
2021-07-03 22:50:03 -07:00
parent 5d39aa92df
commit 28c681aa96
6 changed files with 377 additions and 206 deletions

View File

@@ -2668,6 +2668,9 @@ Returns the path to the live video component of a [live photo](#live_photo). If
**Note**: will also return None if the live video component is missing on disk. It's possible that the original photo may be on disk ([ismissing](#ismissing)==False) but the video component is missing, likely because it has not been downloaded from iCloud.
#### `path_edited_live_photo`
Returns the path to the edited live video component of an edited [live photo](#live_photo). If photo is not a live photo or not edited, returns None.
#### `portrait`
Returns True if photo was taken in iPhone portrait mode, otherwise False.
@@ -2779,11 +2782,11 @@ Returns a JSON representation of all photo info.
Returns a dictionary representation of all photo info.
#### `export()`
`export(dest, *filename, edited=False, live_photo=False, export_as_hardlink=False, overwrite=False, increment=True, sidecar_json=False, sidecar_exiftool=False, sidecar_xmp=False, use_photos_export=False, timeout=120, exiftool=False, use_albums_as_keywords=False, use_persons_as_keywords=False)`
`export(dest, filename=None, edited=False, live_photo=False, export_as_hardlink=False, overwrite=False, increment=True, sidecar_json=False, sidecar_exiftool=False, sidecar_xmp=False, use_photos_export=False, timeout=120, exiftool=False, use_albums_as_keywords=False, use_persons_as_keywords=False)`
Export photo from the Photos library to another destination on disk.
- dest: must be valid destination path as str (or exception raised).
- *filename (optional): name of picture as str; if not provided, will use current filename. **NOTE**: if provided, user must ensure file extension (suffix) is correct. For example, if photo is .CR2 file, edited image may be .jpeg. If you provide an extension different than what the actual file is, export will print a warning but will happily export the photo using the incorrect file extension. e.g. to get the extension of the edited photo, look at [PhotoInfo.path_edited](#path_edited).
- filename (optional): name of picture as str; if not provided, will use current filename. **NOTE**: if provided, user must ensure file extension (suffix) is correct. For example, if photo is .CR2 file, edited image may be .jpeg. If you provide an extension different than what the actual file is, export will print a warning but will happily export the photo using the incorrect file extension. e.g. to get the extension of the edited photo, look at [PhotoInfo.path_edited](#path_edited).
- edited: boolean; if True (default=False), will export the edited version of the photo (or raise exception if no edited version)
- export_as_hardlink: boolean; if True (default=False), will hardlink files instead of copying them
- overwrite: boolean; if True (default=False), will overwrite files if they alreay exist

View File

@@ -1,3 +1,3 @@
""" version info """
__version__ = "0.42.55"
__version__ = "0.42.56"

View File

@@ -19,6 +19,7 @@ import osxmetadata
import photoscript
import yaml
from rich import pretty
import rich.traceback
import osxphotos
@@ -65,6 +66,8 @@ from .utils import expand_and_validate_filepath, load_function
# set via --verbose/-V
VERBOSE = False
rich.traceback.install()
def verbose_(*args, **kwargs):
"""print output if verbose flag set"""
@@ -701,6 +704,12 @@ def cli(ctx, db, json_, debug):
"a value of 0.0 specifies maximum compression. "
f"Defaults to {DEFAULT_JPEG_QUALITY}",
)
# @click.option(
# "--preview",
# is_flag=True,
# help="Export preview image generated by Photos. "
# "This is a lower-resolution image used by Photos to quickly preview the image.",
# )
@click.option(
"--download-missing",
is_flag=True,
@@ -1160,6 +1169,7 @@ def export(
duplicate,
post_command,
post_function,
preview=False,
):
"""Export photos from the Photos database.
Export path DEST is required.
@@ -1726,6 +1736,7 @@ def export(
replace_keywords=replace_keywords,
retry=retry,
export_dir=dest,
export_preview=preview,
)
if post_function:
@@ -2380,6 +2391,7 @@ def export_photo(
replace_keywords=False,
retry=0,
export_dir=None,
export_preview=False,
):
"""Helper function for export that does the actual export
@@ -2421,6 +2433,7 @@ def export_photo(
replace_keywords: if True, --keyword-template replaces keywords instead of adding keywords
retry: retry up to retry # of times if there's an error
export_dir: top-level export directory for {export_dir} template
export_preview: export the preview image generated by Photos
Returns:
list of path(s) of exported photo or None if photo was missing
@@ -2580,6 +2593,7 @@ def export_photo(
replace_keywords=replace_keywords,
retry=retry,
export_dir=export_dir,
export_preview=export_preview,
)
if export_edited and photo.hasadjustments:
@@ -2655,13 +2669,13 @@ def export_photo(
dest=dest,
dry_run=dry_run,
strip=strip,
export_original=export_original,
export_original=False,
missing=missing_edited,
verbose=verbose,
sidecar_flags=sidecar_flags,
sidecar_flags=sidecar_flags if not export_original else 0,
sidecar_drop_ext=sidecar_drop_ext,
export_live=export_live,
export_raw=export_raw,
export_raw=not export_original and export_raw,
export_as_hardlink=export_as_hardlink,
overwrite=overwrite,
exiftool=exiftool,
@@ -2685,6 +2699,7 @@ def export_photo(
replace_keywords=replace_keywords,
retry=retry,
export_dir=export_dir,
export_preview=not export_original and export_preview,
)
return results
@@ -2730,6 +2745,7 @@ def export_photo_with_template(
replace_keywords,
retry,
export_dir,
export_preview,
):
"""Evaluate directory template then export photo to each directory"""
@@ -2789,8 +2805,10 @@ def export_photo_with_template(
try:
export_results = photo.export2(
dest_path,
filename,
original_filename=filename,
edited=edited,
original=export_original,
edited_filename=filename,
sidecar=sidecar_flags,
sidecar_drop_ext=sidecar_drop_ext,
live_photo=export_live,
@@ -2820,6 +2838,7 @@ def export_photo_with_template(
jpeg_ext=jpeg_ext,
replace_keywords=replace_keywords,
render_options=render_options,
preview=export_preview,
)
for warning_ in export_results.exiftool_warning:
verbose_(f"exiftool warning for file {warning_[0]}: {warning_[1]}")
@@ -3949,7 +3968,9 @@ def repl(ctx, cli_obj, db):
)
print(f"\nThe following functions may be helpful:")
print(f"- get_photo(uuid): return a PhotoInfo object for photo with uuid")
print(f"- get_selected(): return list of PhotoInfo objects for photos selected in Photos")
print(
f"- get_selected(): return list of PhotoInfo objects for photos selected in Photos"
)
print(f"- show(photo): open a photo object in the default viewer")
print(
f"- help(object): print help text including list of methods for object; for example, help(PhotosDB)"

View File

@@ -379,7 +379,7 @@ def rename_jpeg_files(files, jpeg_ext, fileutil):
def export(
self,
dest,
*filename,
filename=None,
edited=False,
live_photo=False,
raw_photo=False,
@@ -410,12 +410,12 @@ def export(
silently ignored).
e.g. to get the extension of the edited photo,
reference PhotoInfo.path_edited
edited: (boolean, default=False); if True will export the edited version of the photo
edited: (boolean, default=False); if True will export the edited version of the photo, otherwise exports the original version
(or raise exception if no edited version)
live_photo: (boolean, default=False); if True, will also export the associted .mov for live photos
raw_photo: (boolean, default=False); if True, will also export the associted RAW photo
live_photo: (boolean, default=False); if True, will also export the associated .mov for live photos
raw_photo: (boolean, default=False); if True, will also export the associated RAW photo
export_as_hardlink: (boolean, default=False); if True, will hardlink files instead of copying them
overwrite: (boolean, default=False); if True will overwrite files if they alreay exist
overwrite: (boolean, default=False); if True will overwrite files if they already exist
increment: (boolean, default=True); if True, will increment file name until a non-existant name is found
if overwrite=False and increment=False, export will fail if destination file already exists
sidecar_json: if set will write a json sidecar with data in format readable by exiftool
@@ -449,10 +449,25 @@ def export(
if sidecar_xmp:
sidecar |= SIDECAR_XMP
if not filename:
if not edited:
filename = self.original_filename
else:
original_name = pathlib.Path(self.original_filename)
if self.path_edited:
ext = pathlib.Path(self.path_edited).suffix
else:
uti = self.uti_edited if edited and self.uti_edited else self.uti
ext = get_preferred_uti_extension(uti)
ext = "." + ext
filename = original_name.stem + "_edited" + ext
results = self.export2(
dest,
*filename,
original=not edited,
original_filename=filename,
edited=edited,
edited_filename=filename,
live_photo=live_photo,
raw_photo=raw_photo,
export_as_hardlink=export_as_hardlink,
@@ -466,7 +481,7 @@ def export(
use_persons_as_keywords=use_persons_as_keywords,
keyword_template=keyword_template,
description_template=description_template,
render_options = render_options,
render_options=render_options,
)
return results.exported
@@ -475,8 +490,10 @@ def export(
def export2(
self,
dest,
*filename,
original=True,
original_filename=None,
edited=False,
edited_filename=None,
live_photo=False,
raw_photo=False,
export_as_hardlink=False,
@@ -509,7 +526,9 @@ def export2(
persons=True,
location=True,
replace_keywords=False,
render_options: Optional[RenderOptions] = None
preview=False,
preview_suffix="_preview",
render_options: Optional[RenderOptions] = None,
):
"""export photo, like export but with update and dry_run options
dest: must be valid destination path or exception raised
@@ -521,8 +540,8 @@ def export2(
in which case export will use the extension provided by Photos upon export.
e.g. to get the extension of the edited photo,
reference PhotoInfo.path_edited
original: (boolean, default=True); if True, will export the original version of the photo
edited: (boolean, default=False); if True will export the edited version of the photo
(or raise exception if no edited version)
live_photo: (boolean, default=False); if True, will also export the associated .mov for live photos
raw_photo: (boolean, default=False); if True, will also export the associated RAW photo
export_as_hardlink: (boolean, default=False); if True, will hardlink files instead of copying them
@@ -565,6 +584,8 @@ def export2(
persons: if True, include persons in exported metadata
location: if True, include location in exported metadata
replace_keywords: if True, keyword_template replaces any keywords, otherwise it's additive
preview: if True, also exports preview image
preview_suffix: optional string to append to end of filename for preview images, if not provided, uses "_preview"
render_options: optional osxphotos.phototemplate.RenderOptions instance to specify options for rendering templates
Returns: ExportResults class
@@ -609,109 +630,160 @@ def export2(
self._render_options = render_options or RenderOptions()
# suffix to add to edited files
# e.g. name will be filename_edited.jpg
edited_identifier = "_edited"
# check edited and raise exception trying to export edited version of
# photo that hasn't been edited
export_original = original
export_edited = edited
if edited and not self.hasadjustments:
raise ValueError(
"Photo does not have adjustments, cannot export edited version"
)
# check arguments and get destination path and filename (if provided)
if filename and len(filename) > 2:
raise TypeError(
"Too many positional arguments. Should be at most two: destination, filename."
)
# verify destination is a valid path
if dest is None:
raise ValueError("Destination must not be None")
raise ValueError("dest must not be None")
elif not dry_run and not os.path.isdir(dest):
raise FileNotFoundError("Invalid path passed to export")
if filename and len(filename) == 1:
# if filename passed, use it
fname = filename[0]
else:
# no filename provided so use the default
# if edited file requested, use filename but add _edited
# need to use file extension from edited file as Photos saves a jpeg once edited
if edited and not use_photos_export:
# verify we have a valid path_edited and use that to get filename
if not self.path_edited:
raise FileNotFoundError(
"edited=True but path_edited is none; hasadjustments: "
f" {self.hasadjustments}"
)
edited_name = pathlib.Path(self.path_edited).name
edited_suffix = pathlib.Path(edited_name).suffix
fname = (
pathlib.Path(self.original_filename).stem
+ edited_identifier
+ edited_suffix
)
else:
fname = self.original_filename
original_filename = original_filename or self.original_filename
dest_original = pathlib.Path(dest) / original_filename
uti = self.uti if edited else self.uti_original
if convert_to_jpeg and self.isphoto and uti != "public.jpeg":
# not a jpeg but will convert to jpeg upon export so fix file extension
fname_new = pathlib.Path(fname)
if not edited_filename:
if not edited:
edited_filename = self.original_filename
else:
original_name = pathlib.Path(self.original_filename)
if self.path_edited:
ext = pathlib.Path(self.path_edited).suffix
else:
uti = self.uti_edited if edited and self.uti_edited else self.uti
ext = get_preferred_uti_extension(uti)
ext = "." + ext
edited_filename = original_name.stem + "_edited" + ext
dest_edited = pathlib.Path(dest) / edited_filename
if convert_to_jpeg and self.isphoto:
something_to_convert = False
ext = "." + jpeg_ext if jpeg_ext else ".jpeg"
fname = str(fname_new.parent / f"{fname_new.stem}{ext}")
if export_original and self.uti_original != "public.jpeg":
# not a jpeg but will convert to jpeg upon export so fix file extension
something_to_convert = True
dest_original = dest_original.parent / f"{dest_original.stem}{ext}"
if export_edited and self.uti != "public.jpeg":
# in Big Sur+, edited HEICs are HEIC
something_to_convert = True
dest_edited = dest_edited.parent / f"{dest_edited.stem}{ext}"
convert_to_jpeg = something_to_convert
else:
# nothing to convert
convert_to_jpeg = False
# check destination path
dest = pathlib.Path(dest)
fname = pathlib.Path(fname)
dest = dest / fname
# check to see if file exists and if so, add (1), (2), etc until we find one that works
# Photos checks the stem and adds (1), (2), etc which avoids collision with sidecars
# e.g. exporting sidecar for file1.png and file1.jpeg
# if file1.png exists and exporting file1.jpeg,
# dest will be file1 (1).jpeg even though file1.jpeg doesn't exist to prevent sidecar collision
count = 0
if not update and increment and not overwrite:
count = 1
dest_files = findfiles(f"{dest.stem}*", str(dest.parent))
dest_files = findfiles(f"{dest_original.stem}*", str(dest_original.parent))
dest_files = [pathlib.Path(f).stem.lower() for f in dest_files]
dest_new = dest.stem
dest_new = dest_original.stem
while dest_new.lower() in dest_files:
dest_new = f"{dest.stem} ({count})"
count += 1
dest = dest.parent / f"{dest_new}{dest.suffix}"
dest_new = f"{dest_original.stem} ({count})"
dest_original = dest_original.parent / f"{dest_new}{dest_original.suffix}"
# if overwrite==False and #increment==False, export should fail if file exists
if dest.exists() and not update and not overwrite and not increment:
if (
dest_original.exists()
and export_original
and not update
and not overwrite
and not increment
):
raise FileExistsError(
f"destination exists ({dest}); overwrite={overwrite}, increment={increment}"
f"destination exists ({dest_original}); overwrite={overwrite}, increment={increment}"
)
self._render_options.filepath = str(dest)
if export_edited:
if not update and increment and not overwrite:
dest_files = findfiles(f"{dest_edited.stem}*", str(dest_edited.parent))
dest_files = [pathlib.Path(f).stem.lower() for f in dest_files]
dest_new = dest_edited.stem
if count:
# incremented above when checking original destination
dest_new = f"{dest_new} ({count})"
while dest_new.lower() in dest_files:
count += 1
dest_new = f"{dest.stem} ({count})"
dest_edited = dest_edited.parent / f"{dest_new}{dest_edited.suffix}"
# if overwrite==False and #increment==False, export should fail if file exists
if dest_edited.exists() and not update and not overwrite and not increment:
raise FileExistsError(
f"destination exists ({dest_edited}); overwrite={overwrite}, increment={increment}"
)
self._render_options.filepath = (
str(dest_original) if export_original else str(dest_edited)
)
all_results = ExportResults()
if not use_photos_export:
if use_photos_export:
# TODO: collapse these into a single call (refactor _export_photo_with_photos_export)
if original:
self._export_photo_with_photos_export(
dest_original,
all_results,
fileutil,
export_db,
use_photokit=use_photokit,
dry_run=dry_run,
timeout=timeout,
jpeg_ext=jpeg_ext,
touch_file=touch_file,
update=update,
overwrite=overwrite,
live_photo=live_photo,
edited=False,
convert_to_jpeg=convert_to_jpeg,
jpeg_quality=jpeg_quality,
)
if edited:
self._export_photo_with_photos_export(
dest_edited,
all_results,
fileutil,
export_db,
use_photokit=use_photokit,
dry_run=dry_run,
timeout=timeout,
jpeg_ext=jpeg_ext,
touch_file=touch_file,
update=update,
overwrite=overwrite,
live_photo=live_photo,
edited=True,
convert_to_jpeg=convert_to_jpeg,
jpeg_quality=jpeg_quality,
)
else:
# find the source file on disk and export
# get path to source file and verify it's not None and is valid file
# TODO: how to handle ismissing or not hasadjustments and edited=True cases?
export_src_dest = []
if edited:
if self.path_edited is not None:
src = self.path_edited
export_src_dest.append((self.path_edited, dest_edited))
else:
raise FileNotFoundError(
f"Cannot export edited photo if path_edited is None"
)
else:
if self.path is not None:
src = self.path
export_src_dest.append((self.path, dest_original))
else:
raise FileNotFoundError("Cannot export photo if path is None")
if not os.path.isfile(src):
for src, dest in export_src_dest:
if not pathlib.Path(src).is_file():
raise FileNotFoundError(f"{src} does not appear to exist")
# found source now try to find right destination
@@ -789,12 +861,35 @@ def export2(
)
all_results += results
dest = dest_original if export_original else dest_edited
# copy live photo associated .mov if requested
if live_photo and self.live_photo:
if export_original and live_photo and self.live_photo and self.path_live_photo:
live_name = dest.parent / f"{dest.stem}.mov"
src_live = self.path_live_photo
results = self._export_photo(
src_live,
live_name,
update,
export_db,
overwrite,
export_as_hardlink,
exiftool,
touch_file,
False,
fileutil=fileutil,
ignore_signature=ignore_signature,
)
all_results += results
if src_live is not None:
if (
export_edited
and live_photo
and self.live_photo
and self.path_edited_live_photo
):
live_name = dest.parent / f"{dest_edited.stem}.mov"
src_live = self.path_edited_live_photo
results = self._export_photo(
src_live,
live_name,
@@ -831,26 +926,30 @@ def export2(
ignore_signature=ignore_signature,
)
all_results += results
else:
self._export_photo_with_photos_export(
dest,
filename,
all_results,
fileutil,
# copy preview image if requested
if preview and self.path_derivatives:
# Photos keeps multiple different derivatives and path_derivatives returns list of them
# first derivative is the largest so export that one
preview_path = pathlib.Path(self.path_derivatives[0])
preview_ext = preview_path.suffix
preview_name = dest.parent / f"{dest.stem}{preview_suffix}{preview_ext}"
if preview_path is not None:
results = self._export_photo(
preview_path,
preview_name,
update,
export_db,
use_photokit=use_photokit,
dry_run=dry_run,
timeout=timeout,
jpeg_ext=jpeg_ext,
touch_file=touch_file,
update=update,
overwrite=overwrite,
live_photo=live_photo,
edited=edited,
edited_identifier=edited_identifier,
convert_to_jpeg=convert_to_jpeg,
overwrite,
export_as_hardlink,
exiftool,
touch_file,
convert_to_jpeg,
fileutil=fileutil,
jpeg_quality=jpeg_quality,
ignore_signature=ignore_signature,
)
all_results += results
# export metadata
sidecars = []
@@ -861,6 +960,7 @@ def export2(
sidecar_xmp_files_skipped = []
sidecar_xmp_files_written = []
dest = dest_original if export_original else dest_edited
dest_suffix = "" if sidecar_drop_ext else dest.suffix
if sidecar & SIDECAR_JSON:
sidecar_filename = dest.parent / pathlib.Path(f"{dest.stem}{dest_suffix}.json")
@@ -1111,7 +1211,6 @@ def export2(
def _export_photo_with_photos_export(
self,
dest,
filename,
all_results,
fileutil,
export_db,
@@ -1124,7 +1223,6 @@ def _export_photo_with_photos_export(
overwrite=None,
live_photo=None,
edited=None,
edited_identifier=None,
convert_to_jpeg=None,
jpeg_quality=1.0,
):
@@ -1137,15 +1235,10 @@ def _export_photo_with_photos_export(
# shared photos (in shared albums) show up as not having adjustments (not edited)
# but Photos is unable to export the "original" as only a jpeg copy is shared in iCloud
# so tell Photos to export the current version in this case
if filename:
# use filename stem provided
filestem = dest.stem
else:
# didn't get passed a filename, add _edited
filestem = f"{dest.stem}{edited_identifier}"
uti = self.uti_edited if edited and self.uti_edited else self.uti
ext = get_preferred_uti_extension(uti)
dest = dest.parent / f"{filestem}{ext}"
dest = dest.parent / f"{dest.stem}.{ext}"
if use_photokit:
photolib = PhotoLibrary()
@@ -1190,7 +1283,7 @@ def _export_photo_with_photos_export(
exported = _export_photo_uuid_applescript(
self.uuid,
dest.parent,
filestem=filestem,
filestem=dest.stem,
original=False,
edited=True,
live_photo=live_photo,
@@ -1204,7 +1297,6 @@ def _export_photo_with_photos_export(
all_results.error.append((str(dest), f"{e} ({lineno(__file__)})"))
else:
# export original version and not edited
filestem = dest.stem
if use_photokit:
photolib = PhotoLibrary()
photo = None
@@ -1241,7 +1333,7 @@ def _export_photo_with_photos_export(
exported = _export_photo_uuid_applescript(
self.uuid,
dest.parent,
filestem=filestem,
filestem=dest.stem,
original=True,
edited=False,
live_photo=live_photo,
@@ -1608,7 +1700,9 @@ def _exiftool_dict(
)
if description_template is not None:
options = dataclasses.replace(self._render_options, expand_inplace=True, inplace_sep=", ")
options = dataclasses.replace(
self._render_options, expand_inplace=True, inplace_sep=", "
)
rendered = self.render_template(description_template, options)[0]
description = " ".join(rendered) if rendered else ""
exif["EXIF:ImageDescription"] = description
@@ -1647,7 +1741,9 @@ def _exiftool_dict(
if keyword_template:
rendered_keywords = []
options = dataclasses.replace(self._render_options, none_str=_OSXPHOTOS_NONE_SENTINEL, path_sep="/")
options = dataclasses.replace(
self._render_options, none_str=_OSXPHOTOS_NONE_SENTINEL, path_sep="/"
)
for template_str in keyword_template:
rendered, unmatched = self.render_template(template_str, options)
if unmatched:
@@ -1925,7 +2021,9 @@ def _xmp_sidecar(
extension = extension.suffix[1:] if extension.suffix else None
if description_template is not None:
options = dataclasses.replace(self._render_options, expand_inplace=True, inplace_sep=", ")
options = dataclasses.replace(
self._render_options, expand_inplace=True, inplace_sep=", "
)
rendered = self.render_template(description_template, options)[0]
description = " ".join(rendered) if rendered else ""
else:
@@ -1958,7 +2056,9 @@ def _xmp_sidecar(
if keyword_template:
rendered_keywords = []
options = dataclasses.replace(self._render_options, none_str=_OSXPHOTOS_NONE_SENTINEL, path_sep="/")
options = dataclasses.replace(
self._render_options, none_str=_OSXPHOTOS_NONE_SENTINEL, path_sep="/"
)
for template_str in keyword_template:
rendered, unmatched = self.render_template(template_str, options)
if unmatched:

View File

@@ -342,6 +342,37 @@ class PhotoInfo:
return photopath
@property
def path_edited_live_photo(self):
"""return path to edited version of live photo movie; only valid for Photos 5+"""
if self._db._db_version < _PHOTOS_5_VERSION:
return None
try:
return self._path_edited_live_photo
except AttributeError:
self._path_edited_live_photo = self._path_edited_5_live_photo()
return self._path_edited_live_photo
def _path_edited_5_live_photo(self):
"""return path_edited_live_photo for Photos >= 5"""
if self._db._db_version < _PHOTOS_5_VERSION:
raise RuntimeError("Wrong database format!")
if self.live_photo and self._info["hasAdjustments"]:
library = self._db._library_path
directory = self._uuid[0] # first char of uuid
filename = f"{self._uuid}_2_100_a.mov"
photopath = os.path.join(
library, "resources", "renders", directory, filename
)
if not os.path.isfile(photopath):
photopath = None
else:
photopath = None
return photopath
@property
def path_raw(self):
"""absolute path of associated RAW image or None if there is not one"""

View File

@@ -127,6 +127,8 @@ UUID_DICT_LOCAL = {
"burst_not_selected": "89E235DD-B9AC-4E8D-BDA2-986981CA7582", # IMG_9813.JPG
"burst_default": "F5E6BD24-B493-44E9-BDA2-7AD9D2CC8C9D", # IMG_9816.JPG
"burst_not_default": "75154738-83AA-4DCD-A913-632D5D1C0FEE", # IMG_9814.JPG
"live_edited": "54A01B04-16D7-4FDE-8860-19F2A641E433", # IMG_3203.HEIC
"live": "8EC216A2-0032-4934-BD3F-04C6259B3304", # IMG_3259.HEIC
}
UUID_PUMPKIN_FARM = [
@@ -1273,6 +1275,20 @@ def test_burst_default_pic(photosdb_local):
assert not photo.burst_default_pick
@pytest.mark.skipif(SKIP_TEST, reason="Skip if not running on author's local machine.")
def test_path_edited_live_photo(photosdb_local):
"""test path_edited_live_photo (needs image from local library)"""
photo = photosdb_local.get_photo(UUID_DICT_LOCAL["live_edited"])
assert photo.path_edited_live_photo is not None
@pytest.mark.skipif(SKIP_TEST, reason="Skip if not running on author's local machine.")
def test_path_edited_live_photo_not_edited(photosdb_local):
"""test path_edited_live_photo for a live photo that's not edited (needs image from local library)"""
photo = photosdb_local.get_photo(UUID_DICT_LOCAL["live"])
assert photo.path_edited_live_photo is None
def test_is_reference(photosdb):
"""test isreference"""
@@ -1377,7 +1393,7 @@ def test_duplicates_2(photosdb):
def test_compound_query(photosdb):
""" test photos() with multiple query terms """
"""test photos() with multiple query terms"""
photos = photosdb.photos(persons=["Katie", "Maria"], albums=["Multi Keyword"])
assert len(photos) == 2
@@ -1386,21 +1402,21 @@ def test_compound_query(photosdb):
def test_multi_keyword(photosdb):
""" test photos() with multiple keywords """
"""test photos() with multiple keywords"""
photos = photosdb.photos(keywords=["Kids", "wedding"])
assert len(photos) == 6
def test_multi_album(photosdb):
""" test photos() with multiple albums """
"""test photos() with multiple albums"""
photos = photosdb.photos(albums=["Pumpkin Farm", "Test Album"])
assert len(photos) == 3
def test_multi_uuid(photosdb):
""" test photos() with multiple uuids """
"""test photos() with multiple uuids"""
photos = photosdb.photos(uuid=[UUID_DICT["favorite"], UUID_DICT["not_favorite"]])
assert len(photos) == 2