From 666b6cac33fb8a2d0fc602609f11e190e11c538f Mon Sep 17 00:00:00 2001 From: Rhet Turnbull Date: Fri, 23 Jul 2021 06:11:29 -0700 Subject: [PATCH] Updated docs --- README.md | 4 +- docs/.buildinfo | 2 +- docs/_modules/index.html | 16 +- .../photoinfo/_photoinfo_export.html | 843 ++++--- .../osxphotos/photoinfo/photoinfo.html | 488 ++-- .../_modules/osxphotos/photosdb/photosdb.html | 364 ++- docs/_static/basic.css | 106 +- docs/_static/documentation_options.js | 2 +- docs/_static/searchtools.js | 2 +- docs/_static/underscore-1.13.1.js | 2042 +++++++++++++++++ docs/_static/underscore.js | 8 +- docs/cli.html | 1004 ++++---- docs/genindex.html | 369 ++- docs/index.html | 18 +- docs/modules.html | 10 +- docs/objects.inv | Bin 3460 -> 3724 bytes docs/reference.html | 1018 ++++---- docs/search.html | 18 +- docs/searchindex.js | 2 +- requirements_dev.txt | 5 + 20 files changed, 4556 insertions(+), 1765 deletions(-) create mode 100644 docs/_static/underscore-1.13.1.js create mode 100644 requirements_dev.txt diff --git a/README.md b/README.md index 2fde3555..563ab596 100644 --- a/README.md +++ b/README.md @@ -1823,7 +1823,7 @@ Substitution Description {lf} A line feed: '\n', alias for {newline} {cr} A carriage return: '\r' {crlf} a carriage return + line feed: '\r\n' -{osxphotos_version} The osxphotos version, e.g. '0.42.65' +{osxphotos_version} The osxphotos version, e.g. '0.42.66' {osxphotos_cmd_line} The full command line used to run osxphotos The following substitutions may result in multiple values. Thus if specified for @@ -3672,7 +3672,7 @@ The following template field substitutions are availabe for use the templating s |{lf}|A line feed: '\n', alias for {newline}| |{cr}|A carriage return: '\r'| |{crlf}|a carriage return + line feed: '\r\n'| -|{osxphotos_version}|The osxphotos version, e.g. '0.42.65'| +|{osxphotos_version}|The osxphotos version, e.g. '0.42.66'| |{osxphotos_cmd_line}|The full command line used to run osxphotos| |{album}|Album(s) photo is contained in| |{folder_album}|Folder path + album photo is contained in. e.g. 'Folder/Subfolder/Album' or just 'Album' if no enclosing folder| diff --git a/docs/.buildinfo b/docs/.buildinfo index 8454719b..ba1f918a 100644 --- a/docs/.buildinfo +++ b/docs/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 210ecd9d654dea5d4c21627449ca1d63 +config: 37d92c35f55b2e7d711392f3f43dd1ef tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_modules/index.html b/docs/_modules/index.html index 119b7160..ee61285c 100644 --- a/docs/_modules/index.html +++ b/docs/_modules/index.html @@ -5,10 +5,10 @@ - Overview: module code — osxphotos 0.42.20 documentation - - - + Overview: module code — osxphotos 0.42.66 documentation + + + @@ -31,7 +31,11 @@

All modules for which code is available

-
diff --git a/docs/_modules/osxphotos/photoinfo/_photoinfo_export.html b/docs/_modules/osxphotos/photoinfo/_photoinfo_export.html index 5625415c..1b3531a3 100644 --- a/docs/_modules/osxphotos/photoinfo/_photoinfo_export.html +++ b/docs/_modules/osxphotos/photoinfo/_photoinfo_export.html @@ -5,10 +5,10 @@ - osxphotos.photoinfo._photoinfo_export — osxphotos 0.42.17 documentation - - - + osxphotos.photoinfo._photoinfo_export — osxphotos 0.42.66 documentation + + + @@ -48,6 +48,7 @@ # TODO: should this be its own PhotoExporter class? # TODO: the various sidecar_json, sidecar_xmp, etc args should all be collapsed to a sidecar param using a bit mask +import dataclasses import glob import hashlib import json @@ -57,11 +58,11 @@ import re import tempfile from collections import namedtuple # pylint: disable=syntax-error +from typing import Optional import photoscript from mako.template import Template -# from .._applescript import AppleScript from .._constants import ( _MAX_IPTC_KEYWORD_LEN, _OSXPHOTOS_NONE_SENTINEL, @@ -69,6 +70,8 @@ _UNKNOWN_PERSON, _XMP_TEMPLATE_NAME, _XMP_TEMPLATE_NAME_BETA, + DEFAULT_PREVIEW_SUFFIX, + LIVE_VIDEO_EXTENSIONS, SIDECAR_EXIFTOOL, SIDECAR_JSON, SIDECAR_XMP, @@ -84,20 +87,22 @@ PhotoKitFetchFailed, PhotoLibrary, ) -from ..utils import findfiles, get_preferred_uti_extension, lineno, noop +from ..phototemplate import RenderOptions +from ..uti import get_preferred_uti_extension +from ..utils import findfiles, lineno, noop # retry if use_photos_export fails the first time (which sometimes it does) MAX_PHOTOSCRIPT_RETRIES = 3 class ExportError(Exception): - """ error during export """ + """error during export""" pass class ExportResults: - """ holds export results for export2 """ + """holds export results for export2""" def __init__( self, @@ -152,7 +157,7 @@ self.missing_album = missing_album or [] def all_files(self): - """ return all filenames contained in results """ + """return all filenames contained in results""" files = ( self.exported + self.new @@ -233,7 +238,7 @@ # hexdigest is not a class method, don't import this into PhotoInfo def hexdigest(strval): - """ hexdigest of a string, using blake2b """ + """hexdigest of a string, using blake2b""" h = hashlib.blake2b(digest_size=20) h.update(bytes(strval, "utf-8")) return h.hexdigest() @@ -250,6 +255,7 @@ timeout=120, burst=False, dry_run=False, + overwrite=False, ): """Export photo to dest path using applescript to control Photos If photo is a live photo, exports both the photo and associated .mov file @@ -329,6 +335,8 @@ # use the name Photos provided dest_new = dest / path.name if not dry_run: + if overwrite and dest_new.exists(): + FileUtil.unlink(dest_new) FileUtil.copy(str(path), str(dest_new)) exported_paths.append(str(dest_new)) return exported_paths @@ -376,13 +384,13 @@ # not a class method, don't import into PhotoInfo def rename_jpeg_files(files, jpeg_ext, fileutil): - """ rename any jpeg files in files so that extension matches jpeg_ext + """rename any jpeg files in files so that extension matches jpeg_ext Args: files: list of file paths jpeg_ext: extension to use for jpeg files found in files, e.g. "jpg" fileutil: a FileUtil object - + Returns: list of files with updated names @@ -405,7 +413,7 @@ def export( self, dest, - *filename, + filename=None, edited=False, live_photo=False, raw_photo=False, @@ -422,6 +430,7 @@ use_persons_as_keywords=False, keyword_template=None, description_template=None, + render_options: Optional[RenderOptions] = None, ): """export photo dest: must be valid destination path (or exception raised) @@ -435,12 +444,12 @@ 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 @@ -448,7 +457,7 @@ sidecar_exiftool: if set will write a json sidecar with data in format readable by exiftool sidecar filename will be dest/filename.json; does not include exiftool tag group names (e.g. `exiftool -j`) sidecar_xmp: if set will write an XMP sidecar with IPTC data - sidecar filename will be dest/filename.xmp + sidecar filename will be dest/filename.xmp use_photos_export: (boolean, default=False); if True will attempt to export photo via applescript interaction with Photos timeout: (int, default=120) timeout in seconds used with use_photos_export exiftool: (boolean, default = False); if True, will use exiftool to write metadata to export file @@ -459,7 +468,8 @@ when exporting metadata with exiftool or sidecar keyword_template: (list of strings); list of template strings that will be rendered as used as keywords description_template: string; optional template string that will be rendered for use as photo description - + render_options: an optional osxphotos.phototemplate.RenderOptions instance with options to pass to template renderer + Returns: list of photos exported """ @@ -473,10 +483,25 @@ 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, @@ -490,6 +515,7 @@ use_persons_as_keywords=use_persons_as_keywords, keyword_template=keyword_template, description_template=description_template, + render_options=render_options, ) return results.exported @@ -498,8 +524,10 @@ 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, @@ -532,6 +560,9 @@ persons=True, location=True, replace_keywords=False, + preview=False, + preview_suffix=DEFAULT_PREVIEW_SUFFIX, + 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 @@ -543,12 +574,12 @@ 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 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: bit field: set to one or more of SIDECAR_XMP, SIDECAR_JSON, SIDECAR_EXIFTOOL @@ -587,9 +618,12 @@ 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 + render_options: optional osxphotos.phototemplate.RenderOptions instance to specify options for rendering templates - Returns: ExportResults class - ExportResults has attributes: + Returns: ExportResults class + ExportResults has attributes: "exported", "new", "updated", @@ -609,7 +643,7 @@ "exiftool_warning", "exiftool_error", - + Note: to use dry run mode, you must set dry_run=True and also pass in memory version of export_db, and no-op fileutil (e.g. ExportDBInMemory and FileUtilNoOp) """ @@ -628,208 +662,277 @@ if verbose is None: verbose = self._verbose - # suffix to add to edited files - # e.g. name will be filename_edited.jpg - edited_identifier = "_edited" + self._render_options = render_options or RenderOptions() - # 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 - ) + original_filename = original_filename or self.original_filename + dest_original = pathlib.Path(dest) / original_filename + + if not edited_filename: + if not edited: + edited_filename = self.original_filename else: - fname = self.original_filename + 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 - 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 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}" ) + 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? - if edited: - if self.path_edited is not None: - src = self.path_edited - else: - raise FileNotFoundError( - f"Cannot export edited photo if path_edited is None" - ) - else: - if self.path is not None: - src = self.path - else: - raise FileNotFoundError("Cannot export photo if path is None") + export_src_dest = [] + if edited and self.path_edited is not None: + export_src_dest.append((self.path_edited, dest_edited)) + elif not edited and self.path is not None: + export_src_dest.append((self.path, dest_original)) - if not os.path.isfile(src): - raise FileNotFoundError(f"{src} does not appear to exist") + 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 - if update and dest.exists(): - # destination exists, check to see if destination is the right UUID - dest_uuid = export_db.get_uuid_for_file(dest) - if dest_uuid is None and fileutil.cmp(src, dest): - # might be exporting into a pre-ExportDB folder or the DB got deleted - dest_uuid = self.uuid - export_db.set_data( - filename=dest, - uuid=self.uuid, - orig_stat=fileutil.file_sig(dest), - exif_stat=(None, None, None), - converted_stat=(None, None, None), - edited_stat=(None, None, None), - info_json=self.json(), - exif_json=None, - ) - if dest_uuid != self.uuid: - # not the right file, find the right one - count = 1 - glob_str = str(dest.parent / f"{dest.stem} (*{dest.suffix}") - dest_files = glob.glob(glob_str) - found_match = False - for file_ in dest_files: - dest_uuid = export_db.get_uuid_for_file(file_) - if dest_uuid == self.uuid: - dest = pathlib.Path(file_) - found_match = True - break - elif dest_uuid is None and fileutil.cmp(src, file_): - # files match, update the UUID - dest = pathlib.Path(file_) - found_match = True - export_db.set_data( - filename=dest, - uuid=self.uuid, - orig_stat=fileutil.file_sig(dest), - exif_stat=(None, None, None), - converted_stat=(None, None, None), - edited_stat=(None, None, None), - info_json=self.json(), - exif_json=None, - ) - break - - if not found_match: - # increment the destination file + # found source now try to find right destination + if update and dest.exists(): + # destination exists, check to see if destination is the right UUID + dest_uuid = export_db.get_uuid_for_file(dest) + if dest_uuid is None and fileutil.cmp(src, dest): + # might be exporting into a pre-ExportDB folder or the DB got deleted + dest_uuid = self.uuid + export_db.set_data( + filename=dest, + uuid=self.uuid, + orig_stat=fileutil.file_sig(dest), + exif_stat=(None, None, None), + converted_stat=(None, None, None), + edited_stat=(None, None, None), + info_json=self.json(), + exif_json=None, + ) + if dest_uuid != self.uuid: + # not the right file, find the right one count = 1 - glob_str = str(dest.parent / f"{dest.stem}*") + glob_str = str(dest.parent / f"{dest.stem} (*{dest.suffix}") dest_files = glob.glob(glob_str) - dest_files = [pathlib.Path(f).stem for f in dest_files] - dest_new = dest.stem - while dest_new in dest_files: - dest_new = f"{dest.stem} ({count})" - count += 1 - dest = dest.parent / f"{dest_new}{dest.suffix}" + found_match = False + for file_ in dest_files: + dest_uuid = export_db.get_uuid_for_file(file_) + if dest_uuid == self.uuid: + dest = pathlib.Path(file_) + found_match = True + break + elif dest_uuid is None and fileutil.cmp(src, file_): + # files match, update the UUID + dest = pathlib.Path(file_) + found_match = True + export_db.set_data( + filename=dest, + uuid=self.uuid, + orig_stat=fileutil.file_sig(dest), + exif_stat=(None, None, None), + converted_stat=(None, None, None), + edited_stat=(None, None, None), + info_json=self.json(), + exif_json=None, + ) + break - # export the dest file - results = self._export_photo( - src, - dest, - update, - export_db, - overwrite, - export_as_hardlink, - exiftool, - touch_file, - convert_to_jpeg, - fileutil=fileutil, - edited=edited, - jpeg_quality=jpeg_quality, - ignore_signature=ignore_signature, - ) - all_results += results + if not found_match: + # increment the destination file + count = 1 + glob_str = str(dest.parent / f"{dest.stem}*") + dest_files = glob.glob(glob_str) + dest_files = [pathlib.Path(f).stem for f in dest_files] + dest_new = dest.stem + while dest_new in dest_files: + dest_new = f"{dest.stem} ({count})" + count += 1 + dest = dest.parent / f"{dest_new}{dest.suffix}" + + # export the dest file + results = self._export_photo( + src, + dest, + update, + export_db, + overwrite, + export_as_hardlink, + exiftool, + touch_file, + convert_to_jpeg, + fileutil=fileutil, + edited=edited, + jpeg_quality=jpeg_quality, + ignore_signature=ignore_signature, + ) + 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: - 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 ( + 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, + update, + export_db, + overwrite, + export_as_hardlink, + exiftool, + touch_file, + False, + fileutil=fileutil, + ignore_signature=ignore_signature, + ) + all_results += results # copy associated RAW image if requested - if raw_photo and self.has_raw: + if raw_photo and self.has_raw and self.path_raw: raw_path = pathlib.Path(self.path_raw) raw_ext = raw_path.suffix raw_name = dest.parent / f"{dest.stem}{raw_ext}" @@ -849,140 +952,30 @@ ignore_signature=ignore_signature, ) all_results += results - else: - # TODO: move this big if/else block to separate functions - # e.g. _export_with_photos_export or such - # use_photo_export - # export live_photo .mov file? - live_photo = True if live_photo and self.live_photo else False - if edited or self.shared: - # exported edited version and not original - # 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}" - if use_photokit: - photolib = PhotoLibrary() - photo = None - try: - photo = photolib.fetch_uuid(self.uuid) - except PhotoKitFetchFailed as e: - # if failed to find UUID, might be a burst photo - if self.burst and self._info["burstUUID"]: - bursts = photolib.fetch_burst_uuid( - self._info["burstUUID"], all=True - ) - # PhotoKit UUIDs may contain "/L0/001" so only look at beginning - photo = [p for p in bursts if p.uuid.startswith(self.uuid)] - photo = photo[0] if photo else None - if not photo: - all_results.error.append( - ( - str(dest), - f"PhotoKitFetchFailed exception exporting photo {self.uuid}: {e} ({lineno(__file__)})", - ) - ) - if photo: - if not dry_run: - try: - exported = photo.export( - dest.parent, dest.name, version=PHOTOS_VERSION_CURRENT - ) - all_results.exported.extend(exported) - except Exception as e: - all_results.error.append( - (str(dest), f"{e} ({lineno(__file__)})") - ) - else: - # dry_run, don't actually export - all_results.exported.append(str(dest)) - else: - try: - exported = _export_photo_uuid_applescript( - self.uuid, - dest.parent, - filestem=filestem, - original=False, - edited=True, - live_photo=live_photo, - timeout=timeout, - burst=self.burst, - dry_run=dry_run, - ) - all_results.exported.extend(exported) - except ExportError as e: - 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 - try: - photo = photolib.fetch_uuid(self.uuid) - except PhotoKitFetchFailed: - # if failed to find UUID, might be a burst photo - if self.burst and self._info["burstUUID"]: - bursts = photolib.fetch_burst_uuid( - self._info["burstUUID"], all=True - ) - # PhotoKit UUIDs may contain "/L0/001" so only look at beginning - photo = [p for p in bursts if p.uuid.startswith(self.uuid)] - photo = photo[0] if photo else None - if photo: - if not dry_run: - try: - exported = photo.export( - dest.parent, dest.name, version=PHOTOS_VERSION_ORIGINAL - ) - all_results.exported.extend(exported) - except Exception as e: - all_results.error.append( - (str(dest), f"{e} ({lineno(__file__)})") - ) - else: - # dry_run, don't actually export - all_results.exported.append(str(dest)) - else: - try: - exported = _export_photo_uuid_applescript( - self.uuid, - dest.parent, - filestem=filestem, - original=True, - edited=False, - live_photo=live_photo, - timeout=timeout, - burst=self.burst, - dry_run=dry_run, - ) - all_results.exported.extend(exported) - except ExportError as e: - all_results.error.append((str(dest), f"{e} ({lineno(__file__)})")) - if all_results.exported: - if jpeg_ext: - # use_photos_export (both PhotoKit and AppleScript) don't use the - # file extension provided (instead they use extension for UTI) - # so if jpeg_ext is set, rename any non-conforming jpegs - all_results.exported = rename_jpeg_files( - all_results.exported, jpeg_ext, 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, + overwrite, + export_as_hardlink, + exiftool, + touch_file, + convert_to_jpeg, + fileutil=fileutil, + jpeg_quality=jpeg_quality, + ignore_signature=ignore_signature, ) - if touch_file: - for exported_file in all_results.exported: - all_results.touched.append(exported_file) - ts = int(self.date.timestamp()) - fileutil.utime(exported_file, (ts, ts)) - if update: - all_results.new.extend(all_results.exported) + all_results += results # export metadata sidecars = [] @@ -993,6 +986,7 @@ 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") @@ -1240,6 +1234,189 @@ return all_results +def _export_photo_with_photos_export( + self, + dest, + all_results, + fileutil, + export_db, + use_photokit=None, + dry_run=None, + timeout=None, + jpeg_ext=None, + touch_file=None, + update=None, + overwrite=None, + live_photo=None, + edited=None, + convert_to_jpeg=None, + jpeg_quality=1.0, +): + # TODO: duplicative code with the if edited/else--remove it + # export live_photo .mov file? + live_photo = bool(live_photo and self.live_photo) + overwrite = overwrite or update + if edited or self.shared: + # exported edited version and not original + # 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 + # didn't get passed a filename, add _edited + uti = self.uti_edited if edited and self.uti_edited else self.uti + ext = get_preferred_uti_extension(uti) + dest = dest.parent / f"{dest.stem}.{ext}" + + if use_photokit: + photolib = PhotoLibrary() + photo = None + try: + photo = photolib.fetch_uuid(self.uuid) + except PhotoKitFetchFailed as e: + # if failed to find UUID, might be a burst photo + if self.burst and self._info["burstUUID"]: + bursts = photolib.fetch_burst_uuid( + self._info["burstUUID"], all=True + ) + # PhotoKit UUIDs may contain "/L0/001" so only look at beginning + photo = [p for p in bursts if p.uuid.startswith(self.uuid)] + photo = photo[0] if photo else None + if not photo: + all_results.error.append( + ( + str(dest), + f"PhotoKitFetchFailed exception exporting photo {self.uuid}: {e} ({lineno(__file__)})", + ) + ) + if photo: + if dry_run: + # dry_run, don't actually export + all_results.exported.append(str(dest)) + else: + try: + exported = photo.export( + dest.parent, + dest.name, + version=PHOTOS_VERSION_CURRENT, + overwrite=overwrite, + ) + all_results.exported.extend(exported) + except Exception as e: + all_results.error.append( + (str(dest), f"{e} ({lineno(__file__)})") + ) + else: + try: + exported = _export_photo_uuid_applescript( + self.uuid, + dest.parent, + filestem=dest.stem, + original=False, + edited=True, + live_photo=live_photo, + timeout=timeout, + burst=self.burst, + dry_run=dry_run, + overwrite=overwrite, + ) + all_results.exported.extend(exported) + except ExportError as e: + all_results.error.append((str(dest), f"{e} ({lineno(__file__)})")) + else: + # export original version and not edited + if use_photokit: + photolib = PhotoLibrary() + photo = None + try: + photo = photolib.fetch_uuid(self.uuid) + except PhotoKitFetchFailed: + # if failed to find UUID, might be a burst photo + if self.burst and self._info["burstUUID"]: + bursts = photolib.fetch_burst_uuid( + self._info["burstUUID"], all=True + ) + # PhotoKit UUIDs may contain "/L0/001" so only look at beginning + photo = [p for p in bursts if p.uuid.startswith(self.uuid)] + photo = photo[0] if photo else None + if photo: + if not dry_run: + try: + exported = photo.export( + dest.parent, + dest.name, + version=PHOTOS_VERSION_ORIGINAL, + overwrite=overwrite, + ) + all_results.exported.extend(exported) + except Exception as e: + all_results.error.append( + (str(dest), f"{e} ({lineno(__file__)})") + ) + else: + # dry_run, don't actually export + all_results.exported.append(str(dest)) + else: + try: + exported = _export_photo_uuid_applescript( + self.uuid, + dest.parent, + filestem=dest.stem, + original=True, + edited=False, + live_photo=live_photo, + timeout=timeout, + burst=self.burst, + dry_run=dry_run, + overwrite=overwrite, + ) + all_results.exported.extend(exported) + except ExportError as e: + all_results.error.append((str(dest), f"{e} ({lineno(__file__)})")) + if all_results.exported: + for idx, photopath in enumerate(all_results.exported): + converted_stat = (None, None, None) + photopath = pathlib.Path(photopath) + if convert_to_jpeg and self.isphoto: + # if passed convert_to_jpeg=True, will assume the photo is a photo and not already a jpeg + if photopath.suffix.lower() not in LIVE_VIDEO_EXTENSIONS: + dest_str = photopath.parent / f"{photopath.stem}.jpeg" + fileutil.convert_to_jpeg( + photopath, dest_str, compression_quality=jpeg_quality + ) + converted_stat = fileutil.file_sig(dest_str) + fileutil.unlink(photopath) + all_results.exported[idx] = dest_str + all_results.converted_to_jpeg.append(dest_str) + photopath = dest_str + + photopath = str(photopath) + export_db.set_data( + filename=photopath, + uuid=self.uuid, + orig_stat=fileutil.file_sig(photopath), + exif_stat=(None, None, None), + converted_stat=converted_stat, + edited_stat=(None, None, None), + info_json=self.json(), + exif_json=None, + ) + + # todo: handle signatures + if jpeg_ext: + # use_photos_export (both PhotoKit and AppleScript) don't use the + # file extension provided (instead they use extension for UTI) + # so if jpeg_ext is set, rename any non-conforming jpegs + all_results.exported = rename_jpeg_files( + all_results.exported, jpeg_ext, fileutil + ) + if touch_file: + for exported_file in all_results.exported: + all_results.touched.append(exported_file) + ts = int(self.date.timestamp()) + fileutil.utime(exported_file, (ts, ts)) + if update: + all_results.new.extend(all_results.exported) + + def _export_photo( self, src, @@ -1534,7 +1711,7 @@ QuickTime:GPSCoordinates UserData:GPSCoordinates - Reference: + Reference: https://iptc.org/std/photometadata/specification/IPTC-PhotoMetadata-201610_1.pdf """ @@ -1549,9 +1726,10 @@ ) if description_template is not None: - rendered = self.render_template( - description_template, expand_inplace=True, inplace_sep=", " - )[0] + 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 exif["XMP:Description"] = description @@ -1589,10 +1767,11 @@ if keyword_template: rendered_keywords = [] + 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, none_str=_OSXPHOTOS_NONE_SENTINEL, path_sep="/" - ) + rendered, unmatched = self.render_template(template_str, options) if unmatched: logging.warning( f"Unmatched template substitution for template: {template_str} {unmatched}" @@ -1715,7 +1894,7 @@ def _get_exif_keywords(self): - """ returns list of keywords found in the file's exif metadata """ + """returns list of keywords found in the file's exif metadata""" keywords = [] exif = self.exiftool if exif: @@ -1733,7 +1912,7 @@ def _get_exif_persons(self): - """ returns list of persons found in the file's exif metadata """ + """returns list of persons found in the file's exif metadata""" persons = [] exif = self.exiftool if exif: @@ -1868,9 +2047,10 @@ extension = extension.suffix[1:] if extension.suffix else None if description_template is not None: - rendered = self.render_template( - description_template, expand_inplace=True, inplace_sep=", " - )[0] + 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: description = self.description if self.description is not None else "" @@ -1902,10 +2082,11 @@ if keyword_template: rendered_keywords = [] + 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, none_str=_OSXPHOTOS_NONE_SENTINEL, path_sep="/" - ) + rendered, unmatched = self.render_template(template_str, options) if unmatched: logging.warning( f"Unmatched template substitution for template: {template_str} {unmatched}" @@ -2021,7 +2202,7 @@ ©2021, Rhet Turnbull. | - Powered by Sphinx 3.5.2 + Powered by Sphinx 4.0.2 & Alabaster 0.7.12 diff --git a/docs/_modules/osxphotos/photoinfo/photoinfo.html b/docs/_modules/osxphotos/photoinfo/photoinfo.html index 86a36910..ee4e84fd 100644 --- a/docs/_modules/osxphotos/photoinfo/photoinfo.html +++ b/docs/_modules/osxphotos/photoinfo/photoinfo.html @@ -5,10 +5,10 @@ - osxphotos.photoinfo.photoinfo — osxphotos 0.42.20 documentation - - - + osxphotos.photoinfo.photoinfo — osxphotos 0.42.66 documentation + + + @@ -36,7 +36,6 @@ Represents a single photo in the Photos library and provides access to the photo's attributes PhotosDB.photos() returns a list of PhotoInfo objects """ - import dataclasses import datetime import json @@ -45,6 +44,7 @@ import os.path import pathlib from datetime import timedelta, timezone +from typing import Optional import yaml @@ -67,9 +67,10 @@ from ..adjustmentsinfo import AdjustmentsInfo from ..albuminfo import AlbumInfo, ImportInfo from ..personinfo import FaceInfo, PersonInfo -from ..phototemplate import PhotoTemplate +from ..phototemplate import PhotoTemplate, RenderOptions from ..placeinfo import PlaceInfo4, PlaceInfo5 -from ..utils import _debug, _get_resource_loc, findfiles, get_preferred_uti_extension +from ..uti import get_preferred_uti_extension, get_uti_for_extension +from ..utils import _debug, _get_resource_loc, findfiles
[docs]class PhotoInfo: @@ -79,30 +80,31 @@ """ # import additional methods - from ._photoinfo_searchinfo import ( - search_info, - search_info_normalized, - labels, - labels_normalized, - SearchInfo, - ) - from ._photoinfo_exifinfo import exif_info, ExifInfo + from ._photoinfo_comments import comments, likes + from ._photoinfo_exifinfo import ExifInfo, exif_info from ._photoinfo_exiftool import exiftool from ._photoinfo_export import ( - export, - export2, - _export_photo, + ExportResults, _exiftool_dict, _exiftool_json_sidecar, + _export_photo, + _export_photo_with_photos_export, _get_exif_keywords, _get_exif_persons, _write_exif_data, _write_sidecar, _xmp_sidecar, - ExportResults, + export, + export2, + ) + from ._photoinfo_scoreinfo import ScoreInfo, score + from ._photoinfo_searchinfo import ( + SearchInfo, + labels, + labels_normalized, + search_info, + search_info_normalized, ) - from ._photoinfo_scoreinfo import score, ScoreInfo - from ._photoinfo_comments import comments, likes def __init__(self, db=None, uuid=None, info=None): self._uuid = uuid @@ -110,9 +112,12 @@ self._db = db self._verbose = self._db._verbose + # TODO: remove this once refactor of PhotoExporter is done + self._render_options = RenderOptions() + @property def filename(self): - """ filename of the picture """ + """filename of the picture""" if ( self._db._db_version <= _PHOTOS_4_VERSION and self.has_raw @@ -140,7 +145,7 @@ @property def date(self): - """ image creation date as timezone aware datetime object """ + """image creation date as timezone aware datetime object""" return self._info["imageDate"] @property @@ -166,52 +171,22 @@ @property def tzoffset(self): - """ timezone offset from UTC in seconds """ + """timezone offset from UTC in seconds""" return self._info["imageTimeZoneOffsetSeconds"] @property def path(self): - """ absolute path on disk of the original picture """ + """absolute path on disk of the original picture""" try: return self._path 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 @@ -241,9 +216,40 @@ 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 """ + """absolute path on disk of the edited picture""" """ None if photo has not been edited """ try: @@ -257,7 +263,7 @@ return self._path_edited def _path_edited_5(self): - """ return path_edited for Photos >= 5 """ + """return path_edited for Photos >= 5""" # In Photos 5.0 / Catalina / MacOS 10.15: # edited photos appear to always be converted to .jpeg and stored in # library_name/resources/renders/X/UUID_1_201_a.jpeg @@ -280,14 +286,10 @@ 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" @@ -315,7 +317,7 @@ return photopath def _path_edited_4(self): - """ return path_edited for Photos <= 4 """ + """return path_edited for Photos <= 4""" if self._db._db_version > _PHOTOS_4_VERSION: raise RuntimeError("Wrong database format!") @@ -373,9 +375,40 @@ 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 """ + """absolute path of associated RAW image or None if there is not one""" # In Photos 5, raw is in same folder as original but with _4.ext # Unless "Copy Items to the Photos Library" is not checked @@ -402,60 +435,72 @@ # 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"]) + # 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 files have same name as original but with _4.raw_ext appended + # I believe the _4 maps to PHAssetResourceTypeAlternatePhoto = 4 + # see: https://developer.apple.com/documentation/photokit/phassetresourcetype/phassetresourcetypealternatephoto?language=objc + glob_str = f"{filestem}_4*" raw_file = findfiles(glob_str, filepath) - if len(raw_file) != 1: - # Note: In Photos Version 5.0 (141.19.150), images not copied to Photos Library - # that are missing do not always trigger is_missing = True as happens - # in earlier version so it's possible for this check to fail, if so, return None - logging.debug(f"Error getting path to RAW file: {filepath}/{glob_str}") + if not raw_file: 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 + 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 """ + """long / extended description of picture""" return self._info["extendedDescription"] @property def persons(self): - """ list of persons in picture """ + """list of persons in picture""" return [self._db._dbpersons_pk[pk]["fullname"] for pk in self._info["persons"]] @property def person_info(self): - """ list of PersonInfo objects for person in picture """ + """list of PersonInfo objects for person in picture""" try: return self._personinfo except AttributeError: @@ -466,7 +511,7 @@ @property def face_info(self): - """ list of FaceInfo objects for faces in picture """ + """list of FaceInfo objects for faces in picture""" try: return self._faceinfo except AttributeError: @@ -480,7 +525,7 @@ @property def albums(self): - """ list of albums picture is contained in """ + """list of albums picture is contained in""" try: return self._albums except AttributeError: @@ -492,7 +537,7 @@ @property def burst_albums(self): - """If photo is burst photo, list of albums it is contained in as well as any albums the key photo is contained in, otherwise returns self.albums """ + """If photo is burst photo, list of albums it is contained in as well as any albums the key photo is contained in, otherwise returns self.albums""" try: return self._burst_albums except AttributeError: @@ -505,7 +550,7 @@ @property def album_info(self): - """ list of AlbumInfo objects representing albums the photo is contained in """ + """list of AlbumInfo objects representing albums the photo is contained in""" try: return self._album_info except AttributeError: @@ -517,7 +562,7 @@ @property def burst_album_info(self): - """ If photo is a burst photo, returns list of AlbumInfo objects representing albums the photo is contained in as well as albums the burst key photo is contained in, otherwise returns self.album_info. """ + """If photo is a burst photo, returns list of AlbumInfo objects representing albums the photo is contained in as well as albums the burst key photo is contained in, otherwise returns self.album_info.""" try: return self._burst_album_info except AttributeError: @@ -530,7 +575,7 @@ @property def import_info(self): - """ ImportInfo object representing import session for the photo or None if no import session """ + """ImportInfo object representing import session for the photo or None if no import session""" try: return self._import_info except AttributeError: @@ -543,17 +588,17 @@ @property def keywords(self): - """ list of keywords for picture """ + """list of keywords for picture""" return self._info["keywords"] @property def title(self): - """ name / title of picture """ + """name / title of picture""" return self._info["name"] @property def uuid(self): - """ UUID of picture """ + """UUID of picture""" return self._uuid @property @@ -571,12 +616,12 @@ @property def hasadjustments(self): - """ True if picture has adjustments / edits """ + """True if picture has adjustments / edits""" return self._info["hasAdjustments"] == 1 @property def adjustments(self): - """ Returns AdjustmentsInfo class for adjustment data or None if no adjustments; Photos 5+ only """ + """Returns AdjustmentsInfo class for adjustment data or None if no adjustments; Photos 5+ only""" if self._db._db_version <= _PHOTOS_4_VERSION: return None @@ -600,32 +645,32 @@ @property def external_edit(self): - """ Returns True if picture was edited outside of Photos using external editor """ + """Returns True if picture was edited outside of Photos using external editor""" return self._info["adjustmentFormatID"] == "com.apple.Photos.externalEdit" @property def favorite(self): - """ True if picture is marked as favorite """ + """True if picture is marked as favorite""" return self._info["favorite"] == 1 @property def hidden(self): - """ True if picture is hidden """ + """True if picture is hidden""" return self._info["hidden"] == 1 @property def visible(self): - """ True if picture is visble """ + """True if picture is visble""" return self._info["visible"] @property def intrash(self): - """ True if picture is in trash ('Recently Deleted' folder)""" + """True if picture is in trash ('Recently Deleted' folder)""" return self._info["intrash"] @property def date_trashed(self): - """ Date asset was placed in the trash or None """ + """Date asset was placed in the trash or None""" # TODO: add add_timezone(dt, offset_seconds) to datetime_utils # also update date_modified trasheddate = self._info["trasheddate"] @@ -639,7 +684,7 @@ @property def date_added(self): - """ Date photo was added to the database """ + """Date photo was added to the database""" try: return self._date_added except AttributeError: @@ -656,7 +701,7 @@ @property def location(self): - """ returns (latitude, longitude) as float in degrees or None """ + """returns (latitude, longitude) as float in degrees or None""" return (self._latitude, self._longitude) @property @@ -690,13 +735,23 @@ """Returns Uniform Type Identifier (UTI) for the original image for example: public.jpeg or com.apple.quicktime-movie """ - if self._db._db_version <= _PHOTOS_4_VERSION and self._info["has_raw"]: - return self._info["raw_pair_info"]["UTI"] - elif self.shared: - # TODO: need reliable way to get original UTI for shared - return self.uti - else: - return self._info["UTI_original"] + try: + return self._uti_original + except AttributeError: + if self._db._db_version <= _PHOTOS_4_VERSION and self._info["has_raw"]: + self._uti_original = self._info["raw_pair_info"]["UTI"] + elif self.shared: + # TODO: need reliable way to get original UTI for shared + self._uti_original = self.uti + elif self._db._photos_ver >= 7: + # Monterey+ + self._uti_original = get_uti_for_extension( + pathlib.Path(self.original_filename).suffix + ) + else: + self._uti_original = self._info["UTI_original"] + + return self._uti_original @property def uti_edited(self): @@ -715,7 +770,14 @@ for example: com.canon.cr2-raw-image Returns None if no associated RAW image """ - return self._info["UTI_raw"] + if self._db._photos_ver < 7: + return self._info["UTI_raw"] + + rawpath = self.path_raw + if rawpath: + return get_uti_for_extension(pathlib.Path(rawpath).suffix) + else: + return None @property def ismovie(self): @@ -752,27 +814,27 @@ @property def isreference(self): - """ Returns True if photo is a reference (not copied to the Photos library), otherwise False """ + """Returns True if photo is a reference (not copied to the Photos library), otherwise False""" return self._info["isreference"] @property def burst(self): - """ Returns True if photo is part of a Burst photo set, otherwise False """ + """Returns True if photo is part of a Burst photo set, otherwise False""" return self._info["burst"] @property def burst_selected(self): - """ Returns True if photo is a burst photo and has been selected from the burst set by the user, otherwise False """ + """Returns True if photo is a burst photo and has been selected from the burst set by the user, otherwise False""" return bool(self._info["burstPickType"] & BURST_SELECTED) @property def burst_key(self): - """ Returns True if photo is a burst photo and is the key image for the burst set (the image that Photos shows on top of the burst stack), otherwise False """ + """Returns True if photo is a burst photo and is the key image for the burst set (the image that Photos shows on top of the burst stack), otherwise False""" return bool(self._info["burstPickType"] & BURST_KEY) @property def burst_default_pick(self): - """ Returns True if photo is a burst image and is the photo that Photos selected as the default image for the burst set, otherwise False """ + """Returns True if photo is a burst image and is the photo that Photos selected as the default image for the burst set, otherwise False""" return bool(self._info["burstPickType"] & BURST_DEFAULT_PICK) @property @@ -792,7 +854,7 @@ @property def live_photo(self): - """ Returns True if photo is a live photo, otherwise False """ + """Returns True if photo is a live photo, otherwise False""" return self._info["live_photo"] @property @@ -853,25 +915,40 @@ @property def path_derivatives(self): - """ Return any derivative (preview) images associated with the photo as a list of paths, sorted by file size (largest first) """ - if self._db._db_version <= _PHOTOS_4_VERSION: - return self._path_derivatives_4() + """Return any derivative (preview) images associated with the photo as a list of paths, sorted by file size (largest first)""" + try: + return self._path_derivatives + except AttributeError: + if self._db._db_version <= _PHOTOS_4_VERSION: + self._path_derivatives = self._path_derivatives_4() + return self._path_derivatives - directory = self._uuid[0] # first char of uuid - derivative_path = ( - pathlib.Path(self._db._library_path) - / "resources" - / "derivatives" - / directory - ) - files = derivative_path.glob(f"{self.uuid}*.*") - files = sorted(files, reverse=True, key=lambda f: f.stat().st_size) - # return list of filename but skip .THM files (these are actually low-res thumbnails in JPEG format but with .THM extension) - return [str(filename) for filename in files if filename.suffix != ".THM"] + directory = self._uuid[0] # first char of uuid + derivative_path = ( + pathlib.Path(self._db._library_path) + / "resources" + / "derivatives" + / directory + ) + files = derivative_path.glob(f"{self.uuid}*.*") + files = sorted(files, reverse=True, key=lambda f: f.stat().st_size) + # return list of filename but skip .THM files (these are actually low-res thumbnails in JPEG format but with .THM extension) + derivatives = [ + str(filename) for filename in files if filename.suffix != ".THM" + ] + if ( + self.isphoto + and len(derivatives) > 1 + and derivatives[0].endswith(".mov") + ): + derivatives[1], derivatives[0] = derivatives[0], derivatives[1] + + self._path_derivatives = derivatives + return self._path_derivatives def _path_derivatives_4(self): - """ Return paths to all derivative (preview) files for Photos <= 4""" - modelid = self._info["masterModelID"] + """Return paths to all derivative (preview) files for Photos <= 4""" + modelid = self._info["modelID"] if modelid is None: return [] folder_id, file_id = _get_resource_loc(modelid) @@ -907,42 +984,42 @@ @property def panorama(self): - """ Returns True if photo is a panorama, otherwise False """ + """Returns True if photo is a panorama, otherwise False""" return self._info["panorama"] @property def slow_mo(self): - """ Returns True if photo is a slow motion video, otherwise False """ + """Returns True if photo is a slow motion video, otherwise False""" return self._info["slow_mo"] @property def time_lapse(self): - """ Returns True if photo is a time lapse video, otherwise False """ + """Returns True if photo is a time lapse video, otherwise False""" return self._info["time_lapse"] @property def hdr(self): - """ Returns True if photo is an HDR photo, otherwise False """ + """Returns True if photo is an HDR photo, otherwise False""" return self._info["hdr"] @property def screenshot(self): - """ Returns True if photo is an HDR photo, otherwise False """ + """Returns True if photo is an HDR photo, otherwise False""" return self._info["screenshot"] @property def portrait(self): - """ Returns True if photo is a portrait, otherwise False """ + """Returns True if photo is a portrait, otherwise False""" return self._info["portrait"] @property def selfie(self): - """ Returns True if photo is a selfie (front facing camera), otherwise False """ + """Returns True if photo is a selfie (front facing camera), otherwise False""" return self._info["selfie"] @property def place(self): - """ Returns PlaceInfo object containing reverse geolocation info """ + """Returns PlaceInfo object containing reverse geolocation info""" # implementation note: doesn't create the PlaceInfo object until requested # then memoizes the object in self._place to avoid recreating the object @@ -970,12 +1047,12 @@ @property def has_raw(self): - """ returns True if photo has an associated raw image (that is, it's a RAW+JPEG pair), otherwise False """ + """returns True if photo has an associated raw image (that is, it's a RAW+JPEG pair), otherwise False""" return self._info["has_raw"] @property def israw(self): - """ returns True if photo is a raw image. For images with an associated RAW+JPEG pair, see has_raw """ + """returns True if photo is a raw image. For images with an associated RAW+JPEG pair, see has_raw""" return "raw-image" in self.uti_original @property @@ -987,17 +1064,17 @@ @property def height(self): - """ returns height of the current photo version in pixels """ + """returns height of the current photo version in pixels""" return self._info["height"] @property def width(self): - """ returns width of the current photo version in pixels """ + """returns width of the current photo version in pixels""" return self._info["width"] @property def orientation(self): - """ returns EXIF orientation of the current photo version as int or 0 if current orientation cannot be determined """ + """returns EXIF orientation of the current photo version as int or 0 if current orientation cannot be determined""" if self._db._db_version <= _PHOTOS_4_VERSION: return self._info["orientation"] @@ -1013,76 +1090,63 @@ @property def original_height(self): - """ returns height of the original photo version in pixels """ + """returns height of the original photo version in pixels""" return self._info["original_height"] @property def original_width(self): - """ returns width of the original photo version in pixels """ + """returns width of the original photo version in pixels""" return self._info["original_width"] @property def original_orientation(self): - """ returns EXIF orientation of the original photo version as int """ + """returns EXIF orientation of the original photo version as int""" return self._info["original_orientation"] @property def original_filesize(self): - """ returns filesize of original photo in bytes as int """ + """returns filesize of original photo in bytes as int""" return self._info["original_filesize"] + @property + def duplicates(self): + """return list of PhotoInfo objects for possible duplicates (matching signature of original size, date, height, width) or empty list if no matching duplicates""" + signature = self._db._duplicate_signature(self.uuid) + duplicates = [] + try: + for uuid in self._db._db_signatures[signature]: + if uuid != self.uuid: + # found a possible duplicate + duplicates.append(self._db.get_photo(uuid)) + except KeyError: + # don't expect this to happen as the signature should be in db + logging.warning(f"Did not find signature for {self.uuid} in _db_signatures") + return duplicates +
[docs] def render_template( - self, - template_str, - none_str="_", - path_sep=None, - expand_inplace=False, - inplace_sep=None, - filename=False, - dirname=False, - strip=False, - edited=False, + self, template_str: str, options: Optional[RenderOptions] = None ): """Renders a template string for PhotoInfo instance using PhotoTemplate Args: template_str: a template string with fields to render - none_str: a str to use if template field renders to None, default is "_". - path_sep: a single character str to use as path separator when joining - fields like folder_album; if not provided, defaults to os.path.sep - expand_inplace: expand multi-valued substitutions in-place as a single string - instead of returning individual strings - inplace_sep: optional string to use as separator between multi-valued keywords - with expand_inplace; default is ',' - filename: if True, template output will be sanitized to produce valid file name - dirname: if True, template output will be sanitized to produce valid directory name - strip: if True, strips leading/trailing white space from resulting template - edited: if True, sets {edited_version} field to True, otherwise it gets set to False; set if you want template evaluated for edited version + options: a RenderOptions instance Returns: ([rendered_strings], [unmatched]): tuple of list of rendered strings and list of unmatched template values """ + options = options or RenderOptions() template = PhotoTemplate(self, exiftool_path=self._db._exiftool_path) - return template.render( - template_str, - none_str=none_str, - path_sep=path_sep, - expand_inplace=expand_inplace, - inplace_sep=inplace_sep, - filename=filename, - dirname=dirname, - strip=strip, - edited_version=edited, - )
+ return template.render(template_str, options)
@property def _longitude(self): - """ Returns longitude, in degrees """ + """Returns longitude, in degrees""" return self._info["longitude"] @property def _latitude(self): - """ Returns latitude, in degrees """ + """Returns latitude, in degrees""" return self._info["latitude"] def _get_album_uuids(self): @@ -1120,7 +1184,7 @@ return f"osxphotos.{self.__class__.__name__}(db={self._db}, uuid='{self._uuid}', info={self._info})" def __str__(self): - """ string representation of PhotoInfo object """ + """string representation of PhotoInfo object""" date_iso = self.date.isoformat() date_modified_iso = ( @@ -1183,7 +1247,7 @@ return yaml.dump(info, sort_keys=False)
[docs] def asdict(self): - """ return dict representation """ + """return dict representation""" folders = {album.title: album.folder_names for album in self.album_info} exif = dataclasses.asdict(self.exif_info) if self.exif_info else {} @@ -1259,7 +1323,7 @@ }
[docs] def json(self): - """ Return JSON representation """ + """Return JSON representation""" def default(o): if isinstance(o, (datetime.date, datetime.datetime)): @@ -1268,7 +1332,7 @@ return json.dumps(self.asdict(), sort_keys=True, default=default)
def __eq__(self, other): - """ Compare two PhotoInfo objects for equality """ + """Compare two PhotoInfo objects for equality""" # Can't just compare the two __dicts__ because some methods (like albums) # memoize their value once called in an instance variable (e.g. self._albums) if isinstance(other, self.__class__): @@ -1280,8 +1344,22 @@ return False def __ne__(self, other): - """ Compare two PhotoInfo objects for inequality """ - return not self.__eq__(other) + """Compare two PhotoInfo objects for inequality""" + return not self.__eq__(other) + + def __hash__(self): + """Make PhotoInfo hashable""" + return hash(self.uuid) + + +class PhotoInfoNone: + """mock class that returns None for all attributes""" + + def __init__(self): + pass + + def __getattribute__(self, name): + return None @@ -1340,7 +1418,7 @@ ©2021, Rhet Turnbull. | - Powered by Sphinx 3.5.2 + Powered by Sphinx 4.0.2 & Alabaster 0.7.12 diff --git a/docs/_modules/osxphotos/photosdb/photosdb.html b/docs/_modules/osxphotos/photosdb/photosdb.html index facaf1d5..a09f9ea1 100644 --- a/docs/_modules/osxphotos/photosdb/photosdb.html +++ b/docs/_modules/osxphotos/photosdb/photosdb.html @@ -5,10 +5,10 @@ - osxphotos.photosdb.photosdb — osxphotos 0.42.19 documentation - - - + osxphotos.photosdb.photosdb — osxphotos 0.42.66 documentation + + + @@ -44,11 +44,13 @@ import re import sys import tempfile +from collections import OrderedDict from datetime import datetime, timedelta, timezone from pprint import pformat from typing import List import bitmath +import photoscript from .._constants import ( _DB_TABLE_NAMES, @@ -76,6 +78,7 @@ from ..fileutil import FileUtil from ..personinfo import PersonInfo from ..photoinfo import PhotoInfo +from ..phototemplate import RenderOptions from ..queryoptions import QueryOptions from ..utils import ( _check_file_exists, @@ -95,29 +98,29 @@
[docs]class PhotosDB: - """ Processes a Photos.app library database to extract information about photos """ + """Processes a Photos.app library database to extract information about photos""" # import additional methods + from ._photosdb_process_comments import _process_comments from ._photosdb_process_exif import _process_exifinfo from ._photosdb_process_faceinfo import _process_faceinfo + from ._photosdb_process_scoreinfo import _process_scoreinfo from ._photosdb_process_searchinfo import ( _process_searchinfo, labels, - labels_normalized, labels_as_dict, + labels_normalized, labels_normalized_as_dict, ) - from ._photosdb_process_scoreinfo import _process_scoreinfo - from ._photosdb_process_comments import _process_comments def __init__(self, dbfile=None, verbose=None, exiftool=None): - """ Create a new PhotosDB object. + """Create a new PhotosDB object. Args: dbfile: specify full path to photos library or photos.db; if None, will attempt to locate last library opened by Photos. verbose: optional callable function to use for printing verbose text during processing; if None (default), does not print output. exiftool: optional path to exiftool for methods that require this (e.g. PhotoInfo.exiftool); if not provided, will search PATH - + Raises: FileNotFoundError if dbfile is not a valid Photos library. TypeError if verbose is not None and not callable. @@ -273,6 +276,13 @@ # Will hold the primary key of root folder self._folder_root_pk = None + # Dict to hold signatures for finding possible duplicates + # 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}") @@ -355,7 +365,7 @@ @property def keywords_as_dict(self): - """ return keywords as dict of keyword, count in reverse sorted order (descending) """ + """return keywords as dict of keyword, count in reverse sorted order (descending)""" keywords = { k: len(self._dbkeywords_keyword[k]) for k in self._dbkeywords_keyword.keys() } @@ -365,7 +375,7 @@ @property def persons_as_dict(self): - """ return persons as dict of person, count in reverse sorted order (descending) """ + """return persons as dict of person, count in reverse sorted order (descending)""" persons = {} for pk in self._dbfaces_pk: fullname = self._dbpersons_pk[pk]["fullname"] @@ -378,7 +388,7 @@ @property def albums_as_dict(self): - """ return albums as dict of albums, count in reverse sorted order (descending) """ + """return albums as dict of albums, count in reverse sorted order (descending)""" albums = {} album_keys = self._get_album_uuids(shared=False) for album in album_keys: @@ -395,8 +405,8 @@ @property def albums_shared_as_dict(self): - """ returns shared albums as dict of albums, count in reverse sorted order (descending) - valid only on Photos 5; on Photos <= 4, prints warning and returns empty dict """ + """returns shared albums as dict of albums, count in reverse sorted order (descending) + valid only on Photos 5; on Photos <= 4, prints warning and returns empty dict""" albums = {} album_keys = self._get_album_uuids(shared=True) @@ -414,19 +424,19 @@ @property def keywords(self): - """ return list of keywords found in photos database """ + """return list of keywords found in photos database""" keywords = self._dbkeywords_keyword.keys() return list(keywords) @property def persons(self): - """ return list of persons found in photos database """ + """return list of persons found in photos database""" persons = {self._dbpersons_pk[k]["fullname"] for k in self._dbfaces_pk} return list(persons) @property def person_info(self): - """ return list of PersonInfo objects for each person in the photos database """ + """return list of PersonInfo objects for each person in the photos database""" try: return self._person_info except AttributeError: @@ -437,7 +447,7 @@ @property def folder_info(self): - """ return list FolderInfo objects representing top-level folders in the photos database """ + """return list FolderInfo objects representing top-level folders in the photos database""" if self._db_version <= _PHOTOS_4_VERSION: folders = [ FolderInfo(db=self, uuid=folder) @@ -458,7 +468,7 @@ @property def folders(self): - """ return list of top-level folder names in the photos database """ + """return list of top-level folder names in the photos database""" if self._db_version <= _PHOTOS_4_VERSION: folder_names = [ folder["name"] @@ -479,7 +489,7 @@ @property def album_info(self): - """ return list of AlbumInfo objects for each album in the photos database """ + """return list of AlbumInfo objects for each album in the photos database""" try: return self._album_info except AttributeError: @@ -491,8 +501,8 @@ @property def album_info_shared(self): - """ return list of AlbumInfo objects for each shared album in the photos database - only valid for Photos 5; on Photos <= 4, prints warning and returns empty list """ + """return list of AlbumInfo objects for each shared album in the photos database + only valid for Photos 5; on Photos <= 4, prints warning and returns empty list""" # if _dbalbum_details[key]["cloudownerhashedpersonid"] is not None, then it's a shared album try: return self._album_info_shared @@ -505,7 +515,7 @@ @property def albums(self): - """ return list of albums found in photos database """ + """return list of albums found in photos database""" # Could be more than one album with same name # Right now, they are treated as same album and photos are combined from albums with same name @@ -518,8 +528,8 @@ @property def albums_shared(self): - """ return list of shared albums found in photos database - only valid for Photos 5; on Photos <= 4, prints warning and returns empty list """ + """return list of shared albums found in photos database + only valid for Photos 5; on Photos <= 4, prints warning and returns empty list""" # Could be more than one album with same name # Right now, they are treated as same album and photos are combined from albums with same name @@ -534,7 +544,7 @@ @property def import_info(self): - """ return list of ImportInfo objects for each import session in the database """ + """return list of ImportInfo objects for each import session in the database""" try: return self._import_info except AttributeError: @@ -546,21 +556,21 @@ @property def db_version(self): - """ return the database version as stored in LiGlobals table """ + """return the database version as stored in LiGlobals table""" return self._db_version @property def db_path(self): - """ returns path to the Photos library database PhotosDB was initialized with """ + """returns path to the Photos library database PhotosDB was initialized with""" return os.path.abspath(self._dbfile) @property def library_path(self): - """ returns path to the Photos library PhotosDB was initialized with """ + """returns path to the Photos library PhotosDB was initialized with""" return self._library_path
[docs] def get_db_connection(self): - """ Get connection to the working copy of the Photos database + """Get connection to the working copy of the Photos database Returns: tuple of (connection, cursor) to sqlite3 database @@ -568,7 +578,7 @@ return _open_sql_file(self._tmp_db)
def _copy_db_file(self, fname): - """ copies the sqlite database file to a temp file """ + """copies the sqlite database file to a temp file""" """ returns the name of the temp file """ """ If sqlite shared memory and write-ahead log files exist, those are copied too """ # required because python's sqlite3 implementation can't read a locked file @@ -620,13 +630,15 @@ # return dest_path def _process_database4(self): - """ process the Photos database to extract info - works on Photos version <= 4.0 """ + """process the Photos database to extract info + works on Photos version <= 4.0""" verbose = self._verbose verbose("Processing database.") verbose(f"Database version: {self._db_version}.") + self._photos_ver = 4 # only used in Photos 5+ + (conn, c) = _open_sql_file(self._tmp_db) # get info to associate persons with photos @@ -811,6 +823,8 @@ "creation_date": album[8], "start_date": None, # Photos 5 only "end_date": None, # Photos 5 only + "customsortascending": None, # Photos 5 only + "customsortkey": None, # Photos 5 only } # get details about folders @@ -1123,6 +1137,7 @@ # get info on special types self._dbphotos[uuid]["specialType"] = row[25] self._dbphotos[uuid]["masterModelID"] = row[26] + self._dbphotos[uuid]["pk"] = row[26] # same as masterModelID, to match Photos 5 self._dbphotos[uuid]["panorama"] = True if row[25] == 1 else False self._dbphotos[uuid]["slow_mo"] = True if row[25] == 2 else False self._dbphotos[uuid]["time_lapse"] = True if row[25] == 3 else False @@ -1213,6 +1228,13 @@ self._dbphotos[uuid]["import_uuid"] = row[44] self._dbphotos[uuid]["fok_import_session"] = None + # compute signatures for finding possible duplicates + signature = self._duplicate_signature(uuid) + try: + self._db_signatures[signature].append(uuid) + except KeyError: + self._db_signatures[signature] = [uuid] + # get additional details from RKMaster, needed for RAW processing verbose("Processing additional photo details.") c.execute( @@ -1565,15 +1587,15 @@ logging.debug(pformat(self._dbphotos_burst)) def _build_album_folder_hierarchy_4(self, uuid, folders=None): - """ recursively build folder/album hierarchy - uuid: parent uuid of the album being processed - (parent uuid is a folder in RKFolders) - folders: dict holding the folder hierarchy - NOTE: This implementation is different than _build_album_folder_hierarchy_5 - which takes the uuid of the album being processed. Here uuid is the parent uuid - of the parent folder album because in Photos <=4, folders are in RKFolders and - albums in RKAlbums. In Photos 5, folders are just special albums - with kind = _PHOTOS_5_FOLDER_KIND """ + """recursively build folder/album hierarchy + uuid: parent uuid of the album being processed + (parent uuid is a folder in RKFolders) + folders: dict holding the folder hierarchy + NOTE: This implementation is different than _build_album_folder_hierarchy_5 + which takes the uuid of the album being processed. Here uuid is the parent uuid + of the parent folder album because in Photos <=4, folders are in RKFolders and + albums in RKAlbums. In Photos 5, folders are just special albums + with kind = _PHOTOS_5_FOLDER_KIND""" parent_uuid = self._dbfolder_details[uuid]["parentFolderUuid"] @@ -1596,11 +1618,11 @@ return folders def _process_database5(self): - """ process the Photos database to extract info - works on Photos version 5 and version 6 + """process the Photos database to extract info + works on Photos version 5 and version 6 - This is a big hairy 700 line function that should probably be refactored - but it works so don't touch it. + This is a big hairy 700 line function that should probably be refactored + but it works so don't touch it. """ if _debug(): @@ -1615,10 +1637,14 @@ verbose(f"Database version: {self._db_version}, {photos_ver}.") asset_table = _DB_TABLE_NAMES[photos_ver]["ASSET"] keyword_join = _DB_TABLE_NAMES[photos_ver]["KEYWORD_JOIN"] + asset_album_table = _DB_TABLE_NAMES[photos_ver]["ASSET_ALBUM_TABLE"] album_join = _DB_TABLE_NAMES[photos_ver]["ALBUM_JOIN"] album_sort = _DB_TABLE_NAMES[photos_ver]["ALBUM_SORT_ORDER"] + asset_album_join = _DB_TABLE_NAMES[photos_ver]["ASSET_ALBUM_JOIN"] import_fok = _DB_TABLE_NAMES[photos_ver]["IMPORT_FOK"] depth_state = _DB_TABLE_NAMES[photos_ver]["DEPTH_STATE"] + uti_original_column = _DB_TABLE_NAMES[photos_ver]["UTI_ORIGINAL"] + hdr_type_column = _DB_TABLE_NAMES[photos_ver]["HDR_TYPE"] # Look for all combinations of persons and pictures if _debug(): @@ -1648,7 +1674,11 @@ for person in c: pk = person[0] - fullname = person[2] if person[2] != "" else _UNKNOWN_PERSON + fullname = ( + person[2] + if (person[2] != "" and person[2] is not None) + else _UNKNOWN_PERSON + ) self._dbpersons_pk[pk] = { "pk": pk, "uuid": person[1], @@ -1734,8 +1764,8 @@ {asset_table}.ZUUID, {album_sort} FROM {asset_table} - JOIN Z_26ASSETS ON {album_join} = {asset_table}.Z_PK - JOIN ZGENERICALBUM ON ZGENERICALBUM.Z_PK = Z_26ASSETS.Z_26ALBUMS + JOIN {asset_album_table} ON {album_join} = {asset_table}.Z_PK + JOIN ZGENERICALBUM ON ZGENERICALBUM.Z_PK = {asset_album_join} """ ) @@ -1773,7 +1803,9 @@ "ZTRASHEDSTATE, " # 9 "ZCREATIONDATE, " # 10 "ZSTARTDATE, " # 11 - "ZENDDATE " # 12 + "ZENDDATE, " # 12 + "ZCUSTOMSORTASCENDING, " # 13 + "ZCUSTOMSORTKEY " # 14 "FROM ZGENERICALBUM " ) for album in c: @@ -1793,6 +1825,8 @@ "creation_date": album[10], "start_date": album[11], "end_date": album[12], + "customsortascending": album[13], + "customsortkey": album[14], } # add cross-reference by pk to uuid @@ -1902,7 +1936,7 @@ {asset_table}.ZAVALANCHEUUID, {asset_table}.ZAVALANCHEPICKTYPE, {asset_table}.ZKINDSUBTYPE, - {asset_table}.ZCUSTOMRENDEREDVALUE, + {asset_table}.{hdr_type_column}, ZADDITIONALASSETATTRIBUTES.ZCAMERACAPTUREDEVICE, {asset_table}.ZCLOUDASSETGUID, ZADDITIONALASSETATTRIBUTES.ZREVERSELOCATIONDATA, @@ -1921,7 +1955,8 @@ {asset_table}.ZVISIBILITYSTATE, {asset_table}.ZTRASHEDDATE, {asset_table}.ZSAVEDASSETTYPE, - {asset_table}.ZADDEDDATE + {asset_table}.ZADDEDDATE, + {asset_table}.Z_PK FROM {asset_table} JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK ORDER BY {asset_table}.ZUUID """ @@ -1970,6 +2005,7 @@ # 39 ZGENERICASSET.ZTRASHEDDATE -- date item placed in the trash or null if not in trash # 40 ZGENERICASSET.ZSAVEDASSETTYPE -- how item imported # 41 ZGENERICASSET.ZADDEDDATE -- date item added to the library + # 42 ZGENERICASSET.Z_PK -- primary key for row in c: uuid = row[0] @@ -2154,6 +2190,8 @@ except (ValueError, TypeError): info["added_date"] = datetime(1970, 1, 1) + info["pk"] = row[42] + # initialize import session info which will be filled in later # not every photo has an import session so initialize all records now info["import_session"] = None @@ -2174,6 +2212,13 @@ self._dbphotos[uuid] = info + # compute signatures for finding possible duplicates + signature = self._duplicate_signature(uuid) + try: + self._db_signatures[signature].append(uuid) + except KeyError: + self._db_signatures[signature] = [uuid] + # # if row[19] is not None and ((row[20] == 2) or (row[20] == 4)): # # burst photo # if row[19] is not None: @@ -2259,20 +2304,33 @@ # Get info on remote/local availability for photos in shared albums # Also get UTI of original image (zdatastoresubtype = 1) - c.execute( - f""" SELECT + if self._photos_ver >= 7: + sql_missing = f""" SELECT {asset_table}.ZUUID, ZINTERNALRESOURCE.ZLOCALAVAILABILITY, ZINTERNALRESOURCE.ZREMOTEAVAILABILITY, ZINTERNALRESOURCE.ZDATASTORESUBTYPE, - ZINTERNALRESOURCE.ZUNIFORMTYPEIDENTIFIER, + {uti_original_column}, + null + FROM {asset_table} + JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK + JOIN ZINTERNALRESOURCE ON ZINTERNALRESOURCE.ZASSET = ZADDITIONALASSETATTRIBUTES.ZASSET + WHERE ZDATASTORESUBTYPE = 1 OR ZDATASTORESUBTYPE = 3 """ + else: + sql_missing = f""" SELECT + {asset_table}.ZUUID, + ZINTERNALRESOURCE.ZLOCALAVAILABILITY, + ZINTERNALRESOURCE.ZREMOTEAVAILABILITY, + ZINTERNALRESOURCE.ZDATASTORESUBTYPE, + {uti_original_column}, ZUNIFORMTYPEIDENTIFIER.ZIDENTIFIER FROM {asset_table} JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK JOIN ZINTERNALRESOURCE ON ZINTERNALRESOURCE.ZASSET = ZADDITIONALASSETATTRIBUTES.ZASSET JOIN ZUNIFORMTYPEIDENTIFIER ON ZUNIFORMTYPEIDENTIFIER.Z_PK = ZINTERNALRESOURCE.ZUNIFORMTYPEIDENTIFIER WHERE ZDATASTORESUBTYPE = 1 OR ZDATASTORESUBTYPE = 3 """ - ) + + c.execute(sql_missing) # Order of results: # 0 {asset_table}.ZUUID, @@ -2332,20 +2390,36 @@ # get information about associted RAW images # RAW images have ZDATASTORESUBTYPE = 17 - c.execute( - f""" SELECT + if self._photos_ver >= 7: + sql_raw = f""" SELECT + {asset_table}.ZUUID, + ZINTERNALRESOURCE.ZDATALENGTH, + null, + ZINTERNALRESOURCE.ZDATASTORESUBTYPE, + ZINTERNALRESOURCE.ZRESOURCETYPE, + ZINTERNALRESOURCE.ZFILESYSTEMBOOKMARK + FROM {asset_table} + JOIN ZINTERNALRESOURCE ON ZINTERNALRESOURCE.ZASSET = ZADDITIONALASSETATTRIBUTES.ZASSET + JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK + WHERE ZINTERNALRESOURCE.ZDATASTORESUBTYPE = 17 + """ + else: + sql_raw = f""" SELECT {asset_table}.ZUUID, 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: uuid = row[0] if uuid in self._dbphotos: @@ -2354,6 +2428,33 @@ 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: @@ -2459,9 +2560,9 @@ logging.debug(pformat(self._dbphotos_burst)) def _build_album_folder_hierarchy_5(self, uuid, folders=None): - """ recursively build folder/album hierarchy - uuid: uuid of the album/folder being processed - folders: dict holding the folder hierarchy """ + """recursively build folder/album hierarchy + uuid: uuid of the album/folder being processed + folders: dict holding the folder hierarchy""" # get parent uuid parent = self._dbalbum_details[uuid]["parentfolder"] @@ -2482,17 +2583,17 @@ return folders def _album_folder_hierarchy_list(self, album_uuid): - """ return appropriate album_folder_hierarchy_list for the _db_version """ + """return appropriate album_folder_hierarchy_list for the _db_version""" if self._db_version <= _PHOTOS_4_VERSION: return self._album_folder_hierarchy_list_4(album_uuid) else: return self._album_folder_hierarchy_list_5(album_uuid) def _album_folder_hierarchy_list_4(self, album_uuid): - """ return hierarchical list of folder names album_uuid is contained in - the folder list is in form: - ["Top level folder", "sub folder 1", "sub folder 2"] - returns empty list of album is not in any folders """ + """return hierarchical list of folder names album_uuid is contained in + the folder list is in form: + ["Top level folder", "sub folder 1", "sub folder 2"] + returns empty list of album is not in any folders""" try: folders = self._dbalbum_folders[album_uuid] except KeyError: @@ -2500,7 +2601,7 @@ return [] def _recurse_folder_hierarchy(folders, hierarchy=[]): - """ recursively walk the folders dict to build list of folder hierarchy """ + """recursively walk the folders dict to build list of folder hierarchy""" if not folders: # empty folder dict (album has no folder hierarchy) return [] @@ -2526,10 +2627,10 @@ return hierarchy def _album_folder_hierarchy_list_5(self, album_uuid): - """ return hierarchical list of folder names album_uuid is contained in - the folder list is in form: - ["Top level folder", "sub folder 1", "sub folder 2"] - returns empty list of album is not in any folders """ + """return hierarchical list of folder names album_uuid is contained in + the folder list is in form: + ["Top level folder", "sub folder 1", "sub folder 2"] + returns empty list of album is not in any folders""" try: folders = self._dbalbum_folders[album_uuid] except KeyError: @@ -2537,7 +2638,7 @@ return [] def _recurse_folder_hierarchy(folders, hierarchy=[]): - """ recursively walk the folders dict to build list of folder hierarchy """ + """recursively walk the folders dict to build list of folder hierarchy""" if not folders: # empty folder dict (album has no folder hierarchy) @@ -2569,15 +2670,15 @@ return self._album_folder_hierarchy_folderinfo_5(album_uuid) def _album_folder_hierarchy_folderinfo_4(self, album_uuid): - """ return hierarchical list of FolderInfo objects album_uuid is contained in - ["Top level folder", "sub folder 1", "sub folder 2"] - returns empty list of album is not in any folders """ + """return hierarchical list of FolderInfo objects album_uuid is contained in + ["Top level folder", "sub folder 1", "sub folder 2"] + returns empty list of album is not in any folders""" # title = photosdb._dbalbum_details[album_uuid]["title"] folders = self._dbalbum_folders[album_uuid] # logging.warning(f"uuid = {album_uuid}, folder = {folders}") def _recurse_folder_hierarchy(folders, hierarchy=[]): - """ recursively walk the folders dict to build list of folder hierarchy """ + """recursively walk the folders dict to build list of folder hierarchy""" # logging.warning(f"folders={folders},hierarchy = {hierarchy}") if not folders: # empty folder dict (album has no folder hierarchy) @@ -2603,14 +2704,14 @@ return hierarchy def _album_folder_hierarchy_folderinfo_5(self, album_uuid): - """ return hierarchical list of FolderInfo objects album_uuid is contained in - ["Top level folder", "sub folder 1", "sub folder 2"] - returns empty list of album is not in any folders """ + """return hierarchical list of FolderInfo objects album_uuid is contained in + ["Top level folder", "sub folder 1", "sub folder 2"] + returns empty list of album is not in any folders""" # title = photosdb._dbalbum_details[album_uuid]["title"] folders = self._dbalbum_folders[album_uuid] def _recurse_folder_hierarchy(folders, hierarchy=[]): - """ recursively walk the folders dict to build list of folder hierarchy """ + """recursively walk the folders dict to build list of folder hierarchy""" if not folders: # empty folder dict (album has no folder hierarchy) @@ -2635,19 +2736,19 @@ return hierarchy def _get_album_uuids(self, shared=False, import_session=False): - """ Return list of album UUIDs found in photos database - + """Return list of album UUIDs found in photos database + Filters out albums in the trash and any special album types - + Args: shared: boolean; if True, returns shared albums, else normal albums import_session: boolean, if True, returns import session albums, else normal or shared albums Note: flags (shared, import_session) are mutually exclusive - + Raises: ValueError: raised if mutually exclusive flags passed - Returns: list of album UUIDs + Returns: list of album UUIDs """ if shared and import_session: raise ValueError( @@ -2699,14 +2800,14 @@ return album_list def _get_albums(self, shared=False): - """ Return list of album titles found in photos database + """Return list of album titles found in photos database Albums may have duplicate titles -- these will be treated as a single album. - + Filters out albums in the trash and any special album types Args: shared: boolean; if True, returns shared albums, else normal albums - + Returns: list of album names """ @@ -2725,7 +2826,7 @@ to_date=None, intrash=False, ): - """ Return a list of PhotoInfo objects + """Return a list of PhotoInfo objects If called with no args, returns the entire database of photos If called with args, returns photos matching the args (e.g. keywords, persons, etc.) If more than one arg, returns photos matching all the criteria (e.g. keywords AND persons) @@ -2740,10 +2841,10 @@ persons: list of persons to search for albums: list of album names to search for images: if True, returns image files, if False, does not return images; default is True - movies: if True, returns movie files, if False, does not return movies; default is True + movies: if True, returns movie files, if False, does not return movies; default is True from_date: return photos with creation date >= from_date (datetime.datetime object, default None) to_date: return photos with creation date <= to_date (datetime.datetime object, default None) - intrash: if True, returns only images in "Recently deleted items" folder, + intrash: if True, returns only images in "Recently deleted items" folder, if False returns only photos that aren't deleted; default is False Returns: @@ -2850,7 +2951,7 @@ return photoinfo
[docs] def get_photo(self, uuid): - """ Returns a single photo matching uuid + """Returns a single photo matching uuid Arguments: uuid: the UUID of photo to get @@ -2865,7 +2966,7 @@ # TODO: add to docs and test
[docs] def photos_by_uuid(self, uuids): - """ Returns a list of photos with UUID in uuids. + """Returns a list of photos with UUID in uuids. Does not generate error if invalid or missing UUID passed. This is faster than using PhotosDB.photos if you have list of UUIDs. Returns photos regardless of intrash state. @@ -3217,11 +3318,12 @@ if options.regex: flags = re.IGNORECASE if options.ignore_case else 0 + render_options = RenderOptions(none_str="") for regex, template in options.regex: regex = re.compile(regex, flags) photo_list = [] for p in photos: - rendered, _ = p.render_template(template, none_str="") + rendered, _ = p.render_template(template, render_options) for value in rendered: if regex.search(value): photo_list.append(p) @@ -3236,8 +3338,66 @@ except Exception as e: raise ValueError(f"Invalid query_eval CRITERIA: {e}") + if options.duplicate: + no_date = datetime(1970, 1, 1) + tz = timezone(timedelta(0)) + no_date = no_date.astimezone(tz=tz) + photos = sorted( + [p for p in photos if p.duplicates], + key=lambda x: x.date_added or no_date, + ) + # gather all duplicates but ensure each uuid is only represented once + photodict = OrderedDict() + for p in photos: + if p.uuid not in photodict: + photodict[p.uuid] = p + for d in sorted( + p.duplicates, key=lambda x: x.date_added or no_date + ): + if d.uuid not in photodict: + photodict[d.uuid] = d + photos = list(photodict.values()) + + # filter for deleted as photo.duplicates will include photos in the trash + if not (options.deleted or options.deleted_only): + photos = [p for p in photos if not p.intrash] + if options.deleted_only: + photos = [p for p in photos if p.intrash] + + if options.location: + photos = [p for p in photos if p.location != (None, None)] + elif options.no_location: + photos = [p for p in photos if p.location == (None, None)] + + if options.selected: + # photos selected in Photos app + try: + # catch AppleScript errors as the scripting interfce to Photos is flaky + selected = photoscript.PhotosLibrary().selection + selected_uuid = [p.uuid for p in selected] + photos = [p for p in photos if p.uuid in selected_uuid] + except Exception: + # no photos selected or a selected photo was "open" + # selection only works if photos selected in main media browser + photos = [] + + if options.function: + for function in options.function: + photos = function[0](photos) + return photos
+ def _duplicate_signature(self, uuid): + """Compute a signature for finding possible duplicates""" + return ( + self._dbphotos[uuid]["original_filesize"], + self._dbphotos[uuid]["imageDate"], + self._dbphotos[uuid]["height"], + self._dbphotos[uuid]["width"], + self._dbphotos[uuid]["UTI"], + self._dbphotos[uuid]["hasAdjustments"], + ) + def __repr__(self): return f"osxphotos.{self.__class__.__name__}(dbfile='{self.db_path}')" @@ -3249,8 +3409,8 @@ return False def __len__(self): - """ Returns number of photos in the database - Includes recently deleted photos and non-selected burst images + """Returns number of photos in the database + Includes recently deleted photos and non-selected burst images """ return len(self._dbphotos)
@@ -3280,7 +3440,7 @@ else: for x in values: photos_search.extend(p for p in photos if x in getattr(p, attribute)) - return photos_search + return list(set(photos_search)) @@ -3339,7 +3499,7 @@ ©2021, Rhet Turnbull. | - Powered by Sphinx 3.5.2 + Powered by Sphinx 4.0.2 & Alabaster 0.7.12 diff --git a/docs/_static/basic.css b/docs/_static/basic.css index be19270e..aa9df316 100644 --- a/docs/_static/basic.css +++ b/docs/_static/basic.css @@ -130,7 +130,7 @@ ul.search li a { font-weight: bold; } -ul.search li div.context { +ul.search li p.context { color: #888; margin: 2px 0 0 30px; text-align: left; @@ -277,25 +277,25 @@ p.rubric { font-weight: bold; } -img.align-left, .figure.align-left, object.align-left { +img.align-left, figure.align-left, .figure.align-left, object.align-left { clear: left; float: left; margin-right: 1em; } -img.align-right, .figure.align-right, object.align-right { +img.align-right, figure.align-right, .figure.align-right, object.align-right { clear: right; float: right; margin-left: 1em; } -img.align-center, .figure.align-center, object.align-center { +img.align-center, figure.align-center, .figure.align-center, object.align-center { display: block; margin-left: auto; margin-right: auto; } -img.align-default, .figure.align-default { +img.align-default, figure.align-default, .figure.align-default { display: block; margin-left: auto; margin-right: auto; @@ -319,7 +319,8 @@ img.align-default, .figure.align-default { /* -- sidebars -------------------------------------------------------------- */ -div.sidebar { +div.sidebar, +aside.sidebar { margin: 0 0 0.5em 1em; border: 1px solid #ddb; padding: 7px; @@ -377,12 +378,14 @@ div.body p.centered { /* -- content of sidebars/topics/admonitions -------------------------------- */ div.sidebar > :last-child, +aside.sidebar > :last-child, div.topic > :last-child, div.admonition > :last-child { margin-bottom: 0; } div.sidebar::after, +aside.sidebar::after, div.topic::after, div.admonition::after, blockquote::after { @@ -455,20 +458,22 @@ td > :last-child { /* -- figures --------------------------------------------------------------- */ -div.figure { +div.figure, figure { margin: 0.5em; padding: 0.5em; } -div.figure p.caption { +div.figure p.caption, figcaption { padding: 0.3em; } -div.figure p.caption span.caption-number { +div.figure p.caption span.caption-number, +figcaption span.caption-number { font-style: italic; } -div.figure p.caption span.caption-text { +div.figure p.caption span.caption-text, +figcaption span.caption-text { } /* -- field list styles ----------------------------------------------------- */ @@ -503,6 +508,63 @@ table.hlist td { vertical-align: top; } +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + /* -- other body styles ----------------------------------------------------- */ @@ -629,14 +691,6 @@ dl.glossary dt { font-size: 1.1em; } -.optional { - font-size: 1.3em; -} - -.sig-paren { - font-size: larger; -} - .versionmodified { font-style: italic; } @@ -766,7 +820,11 @@ div.code-block-caption code { table.highlighttable td.linenos, span.linenos, div.doctest > div.highlight span.gp { /* gp: Generic.Prompt */ - user-select: none; + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ } div.code-block-caption span.caption-number { @@ -781,16 +839,6 @@ div.literal-block-wrapper { margin: 1em 0; } -code.descname { - background-color: transparent; - font-weight: bold; - font-size: 1.2em; -} - -code.descclassname { - background-color: transparent; -} - code.xref, a code { background-color: transparent; font-weight: bold; diff --git a/docs/_static/documentation_options.js b/docs/_static/documentation_options.js index 47294564..056afe68 100644 --- a/docs/_static/documentation_options.js +++ b/docs/_static/documentation_options.js @@ -1,6 +1,6 @@ var DOCUMENTATION_OPTIONS = { URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '0.42.20', + VERSION: '0.42.66', LANGUAGE: 'None', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/docs/_static/searchtools.js b/docs/_static/searchtools.js index 1a90152e..e09f9263 100644 --- a/docs/_static/searchtools.js +++ b/docs/_static/searchtools.js @@ -509,7 +509,7 @@ var Search = { var excerpt = ((start > 0) ? '...' : '') + $.trim(text.substr(start, 240)) + ((start + 240 - text.length) ? '...' : ''); - var rv = $('
').text(excerpt); + var rv = $('

').text(excerpt); $.each(hlwords, function() { rv = rv.highlightText(this, 'highlighted'); }); diff --git a/docs/_static/underscore-1.13.1.js b/docs/_static/underscore-1.13.1.js new file mode 100644 index 00000000..ffd77af9 --- /dev/null +++ b/docs/_static/underscore-1.13.1.js @@ -0,0 +1,2042 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define('underscore', factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, (function () { + var current = global._; + var exports = global._ = factory(); + exports.noConflict = function () { global._ = current; return exports; }; + }())); +}(this, (function () { + // Underscore.js 1.13.1 + // https://underscorejs.org + // (c) 2009-2021 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and Investigative Reporters & Editors + // Underscore may be freely distributed under the MIT license. + + // Current version. + var VERSION = '1.13.1'; + + // Establish the root object, `window` (`self`) in the browser, `global` + // on the server, or `this` in some virtual machines. We use `self` + // instead of `window` for `WebWorker` support. + var root = typeof self == 'object' && self.self === self && self || + typeof global == 'object' && global.global === global && global || + Function('return this')() || + {}; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype; + var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null; + + // Create quick reference variables for speed access to core prototypes. + var push = ArrayProto.push, + slice = ArrayProto.slice, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // Modern feature detection. + var supportsArrayBuffer = typeof ArrayBuffer !== 'undefined', + supportsDataView = typeof DataView !== 'undefined'; + + // All **ECMAScript 5+** native function implementations that we hope to use + // are declared here. + var nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeCreate = Object.create, + nativeIsView = supportsArrayBuffer && ArrayBuffer.isView; + + // Create references to these builtin functions because we override them. + var _isNaN = isNaN, + _isFinite = isFinite; + + // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. + var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); + var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', + 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; + + // The largest integer that can be represented exactly. + var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; + + // Some functions take a variable number of arguments, or a few expected + // arguments at the beginning and then a variable number of values to operate + // on. This helper accumulates all remaining arguments past the function’s + // argument length (or an explicit `startIndex`), into an array that becomes + // the last argument. Similar to ES6’s "rest parameter". + function restArguments(func, startIndex) { + startIndex = startIndex == null ? func.length - 1 : +startIndex; + return function() { + var length = Math.max(arguments.length - startIndex, 0), + rest = Array(length), + index = 0; + for (; index < length; index++) { + rest[index] = arguments[index + startIndex]; + } + switch (startIndex) { + case 0: return func.call(this, rest); + case 1: return func.call(this, arguments[0], rest); + case 2: return func.call(this, arguments[0], arguments[1], rest); + } + var args = Array(startIndex + 1); + for (index = 0; index < startIndex; index++) { + args[index] = arguments[index]; + } + args[startIndex] = rest; + return func.apply(this, args); + }; + } + + // Is a given variable an object? + function isObject(obj) { + var type = typeof obj; + return type === 'function' || type === 'object' && !!obj; + } + + // Is a given value equal to null? + function isNull(obj) { + return obj === null; + } + + // Is a given variable undefined? + function isUndefined(obj) { + return obj === void 0; + } + + // Is a given value a boolean? + function isBoolean(obj) { + return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; + } + + // Is a given value a DOM element? + function isElement(obj) { + return !!(obj && obj.nodeType === 1); + } + + // Internal function for creating a `toString`-based type tester. + function tagTester(name) { + var tag = '[object ' + name + ']'; + return function(obj) { + return toString.call(obj) === tag; + }; + } + + var isString = tagTester('String'); + + var isNumber = tagTester('Number'); + + var isDate = tagTester('Date'); + + var isRegExp = tagTester('RegExp'); + + var isError = tagTester('Error'); + + var isSymbol = tagTester('Symbol'); + + var isArrayBuffer = tagTester('ArrayBuffer'); + + var isFunction = tagTester('Function'); + + // Optimize `isFunction` if appropriate. Work around some `typeof` bugs in old + // v8, IE 11 (#1621), Safari 8 (#1929), and PhantomJS (#2236). + var nodelist = root.document && root.document.childNodes; + if (typeof /./ != 'function' && typeof Int8Array != 'object' && typeof nodelist != 'function') { + isFunction = function(obj) { + return typeof obj == 'function' || false; + }; + } + + var isFunction$1 = isFunction; + + var hasObjectTag = tagTester('Object'); + + // In IE 10 - Edge 13, `DataView` has string tag `'[object Object]'`. + // In IE 11, the most common among them, this problem also applies to + // `Map`, `WeakMap` and `Set`. + var hasStringTagBug = ( + supportsDataView && hasObjectTag(new DataView(new ArrayBuffer(8))) + ), + isIE11 = (typeof Map !== 'undefined' && hasObjectTag(new Map)); + + var isDataView = tagTester('DataView'); + + // In IE 10 - Edge 13, we need a different heuristic + // to determine whether an object is a `DataView`. + function ie10IsDataView(obj) { + return obj != null && isFunction$1(obj.getInt8) && isArrayBuffer(obj.buffer); + } + + var isDataView$1 = (hasStringTagBug ? ie10IsDataView : isDataView); + + // Is a given value an array? + // Delegates to ECMA5's native `Array.isArray`. + var isArray = nativeIsArray || tagTester('Array'); + + // Internal function to check whether `key` is an own property name of `obj`. + function has$1(obj, key) { + return obj != null && hasOwnProperty.call(obj, key); + } + + var isArguments = tagTester('Arguments'); + + // Define a fallback version of the method in browsers (ahem, IE < 9), where + // there isn't any inspectable "Arguments" type. + (function() { + if (!isArguments(arguments)) { + isArguments = function(obj) { + return has$1(obj, 'callee'); + }; + } + }()); + + var isArguments$1 = isArguments; + + // Is a given object a finite number? + function isFinite$1(obj) { + return !isSymbol(obj) && _isFinite(obj) && !isNaN(parseFloat(obj)); + } + + // Is the given value `NaN`? + function isNaN$1(obj) { + return isNumber(obj) && _isNaN(obj); + } + + // Predicate-generating function. Often useful outside of Underscore. + function constant(value) { + return function() { + return value; + }; + } + + // Common internal logic for `isArrayLike` and `isBufferLike`. + function createSizePropertyCheck(getSizeProperty) { + return function(collection) { + var sizeProperty = getSizeProperty(collection); + return typeof sizeProperty == 'number' && sizeProperty >= 0 && sizeProperty <= MAX_ARRAY_INDEX; + } + } + + // Internal helper to generate a function to obtain property `key` from `obj`. + function shallowProperty(key) { + return function(obj) { + return obj == null ? void 0 : obj[key]; + }; + } + + // Internal helper to obtain the `byteLength` property of an object. + var getByteLength = shallowProperty('byteLength'); + + // Internal helper to determine whether we should spend extensive checks against + // `ArrayBuffer` et al. + var isBufferLike = createSizePropertyCheck(getByteLength); + + // Is a given value a typed array? + var typedArrayPattern = /\[object ((I|Ui)nt(8|16|32)|Float(32|64)|Uint8Clamped|Big(I|Ui)nt64)Array\]/; + function isTypedArray(obj) { + // `ArrayBuffer.isView` is the most future-proof, so use it when available. + // Otherwise, fall back on the above regular expression. + return nativeIsView ? (nativeIsView(obj) && !isDataView$1(obj)) : + isBufferLike(obj) && typedArrayPattern.test(toString.call(obj)); + } + + var isTypedArray$1 = supportsArrayBuffer ? isTypedArray : constant(false); + + // Internal helper to obtain the `length` property of an object. + var getLength = shallowProperty('length'); + + // Internal helper to create a simple lookup structure. + // `collectNonEnumProps` used to depend on `_.contains`, but this led to + // circular imports. `emulatedSet` is a one-off solution that only works for + // arrays of strings. + function emulatedSet(keys) { + var hash = {}; + for (var l = keys.length, i = 0; i < l; ++i) hash[keys[i]] = true; + return { + contains: function(key) { return hash[key]; }, + push: function(key) { + hash[key] = true; + return keys.push(key); + } + }; + } + + // Internal helper. Checks `keys` for the presence of keys in IE < 9 that won't + // be iterated by `for key in ...` and thus missed. Extends `keys` in place if + // needed. + function collectNonEnumProps(obj, keys) { + keys = emulatedSet(keys); + var nonEnumIdx = nonEnumerableProps.length; + var constructor = obj.constructor; + var proto = isFunction$1(constructor) && constructor.prototype || ObjProto; + + // Constructor is a special case. + var prop = 'constructor'; + if (has$1(obj, prop) && !keys.contains(prop)) keys.push(prop); + + while (nonEnumIdx--) { + prop = nonEnumerableProps[nonEnumIdx]; + if (prop in obj && obj[prop] !== proto[prop] && !keys.contains(prop)) { + keys.push(prop); + } + } + } + + // Retrieve the names of an object's own properties. + // Delegates to **ECMAScript 5**'s native `Object.keys`. + function keys(obj) { + if (!isObject(obj)) return []; + if (nativeKeys) return nativeKeys(obj); + var keys = []; + for (var key in obj) if (has$1(obj, key)) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + } + + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. + function isEmpty(obj) { + if (obj == null) return true; + // Skip the more expensive `toString`-based type checks if `obj` has no + // `.length`. + var length = getLength(obj); + if (typeof length == 'number' && ( + isArray(obj) || isString(obj) || isArguments$1(obj) + )) return length === 0; + return getLength(keys(obj)) === 0; + } + + // Returns whether an object has a given set of `key:value` pairs. + function isMatch(object, attrs) { + var _keys = keys(attrs), length = _keys.length; + if (object == null) return !length; + var obj = Object(object); + for (var i = 0; i < length; i++) { + var key = _keys[i]; + if (attrs[key] !== obj[key] || !(key in obj)) return false; + } + return true; + } + + // If Underscore is called as a function, it returns a wrapped object that can + // be used OO-style. This wrapper holds altered versions of all functions added + // through `_.mixin`. Wrapped objects may be chained. + function _$1(obj) { + if (obj instanceof _$1) return obj; + if (!(this instanceof _$1)) return new _$1(obj); + this._wrapped = obj; + } + + _$1.VERSION = VERSION; + + // Extracts the result from a wrapped and chained object. + _$1.prototype.value = function() { + return this._wrapped; + }; + + // Provide unwrapping proxies for some methods used in engine operations + // such as arithmetic and JSON stringification. + _$1.prototype.valueOf = _$1.prototype.toJSON = _$1.prototype.value; + + _$1.prototype.toString = function() { + return String(this._wrapped); + }; + + // Internal function to wrap or shallow-copy an ArrayBuffer, + // typed array or DataView to a new view, reusing the buffer. + function toBufferView(bufferSource) { + return new Uint8Array( + bufferSource.buffer || bufferSource, + bufferSource.byteOffset || 0, + getByteLength(bufferSource) + ); + } + + // We use this string twice, so give it a name for minification. + var tagDataView = '[object DataView]'; + + // Internal recursive comparison function for `_.isEqual`. + function eq(a, b, aStack, bStack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal). + if (a === b) return a !== 0 || 1 / a === 1 / b; + // `null` or `undefined` only equal to itself (strict comparison). + if (a == null || b == null) return false; + // `NaN`s are equivalent, but non-reflexive. + if (a !== a) return b !== b; + // Exhaust primitive checks + var type = typeof a; + if (type !== 'function' && type !== 'object' && typeof b != 'object') return false; + return deepEq(a, b, aStack, bStack); + } + + // Internal recursive comparison function for `_.isEqual`. + function deepEq(a, b, aStack, bStack) { + // Unwrap any wrapped objects. + if (a instanceof _$1) a = a._wrapped; + if (b instanceof _$1) b = b._wrapped; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className !== toString.call(b)) return false; + // Work around a bug in IE 10 - Edge 13. + if (hasStringTagBug && className == '[object Object]' && isDataView$1(a)) { + if (!isDataView$1(b)) return false; + className = tagDataView; + } + switch (className) { + // These types are compared by value. + case '[object RegExp]': + // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return '' + a === '' + b; + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. + // Object(NaN) is equivalent to NaN. + if (+a !== +a) return +b !== +b; + // An `egal` comparison is performed for other numeric values. + return +a === 0 ? 1 / +a === 1 / b : +a === +b; + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a === +b; + case '[object Symbol]': + return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b); + case '[object ArrayBuffer]': + case tagDataView: + // Coerce to typed array so we can fall through. + return deepEq(toBufferView(a), toBufferView(b), aStack, bStack); + } + + var areArrays = className === '[object Array]'; + if (!areArrays && isTypedArray$1(a)) { + var byteLength = getByteLength(a); + if (byteLength !== getByteLength(b)) return false; + if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) return true; + areArrays = true; + } + if (!areArrays) { + if (typeof a != 'object' || typeof b != 'object') return false; + + // Objects with different constructors are not equivalent, but `Object`s or `Array`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(isFunction$1(aCtor) && aCtor instanceof aCtor && + isFunction$1(bCtor) && bCtor instanceof bCtor) + && ('constructor' in a && 'constructor' in b)) { + return false; + } + } + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + + // Initializing stack of traversed objects. + // It's done here since we only need them for objects and arrays comparison. + aStack = aStack || []; + bStack = bStack || []; + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] === a) return bStack[length] === b; + } + + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + + // Recursively compare objects and arrays. + if (areArrays) { + // Compare array lengths to determine if a deep comparison is necessary. + length = a.length; + if (length !== b.length) return false; + // Deep compare the contents, ignoring non-numeric properties. + while (length--) { + if (!eq(a[length], b[length], aStack, bStack)) return false; + } + } else { + // Deep compare objects. + var _keys = keys(a), key; + length = _keys.length; + // Ensure that both objects contain the same number of properties before comparing deep equality. + if (keys(b).length !== length) return false; + while (length--) { + // Deep compare each member + key = _keys[length]; + if (!(has$1(b, key) && eq(a[key], b[key], aStack, bStack))) return false; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + return true; + } + + // Perform a deep comparison to check if two objects are equal. + function isEqual(a, b) { + return eq(a, b); + } + + // Retrieve all the enumerable property names of an object. + function allKeys(obj) { + if (!isObject(obj)) return []; + var keys = []; + for (var key in obj) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + } + + // Since the regular `Object.prototype.toString` type tests don't work for + // some types in IE 11, we use a fingerprinting heuristic instead, based + // on the methods. It's not great, but it's the best we got. + // The fingerprint method lists are defined below. + function ie11fingerprint(methods) { + var length = getLength(methods); + return function(obj) { + if (obj == null) return false; + // `Map`, `WeakMap` and `Set` have no enumerable keys. + var keys = allKeys(obj); + if (getLength(keys)) return false; + for (var i = 0; i < length; i++) { + if (!isFunction$1(obj[methods[i]])) return false; + } + // If we are testing against `WeakMap`, we need to ensure that + // `obj` doesn't have a `forEach` method in order to distinguish + // it from a regular `Map`. + return methods !== weakMapMethods || !isFunction$1(obj[forEachName]); + }; + } + + // In the interest of compact minification, we write + // each string in the fingerprints only once. + var forEachName = 'forEach', + hasName = 'has', + commonInit = ['clear', 'delete'], + mapTail = ['get', hasName, 'set']; + + // `Map`, `WeakMap` and `Set` each have slightly different + // combinations of the above sublists. + var mapMethods = commonInit.concat(forEachName, mapTail), + weakMapMethods = commonInit.concat(mapTail), + setMethods = ['add'].concat(commonInit, forEachName, hasName); + + var isMap = isIE11 ? ie11fingerprint(mapMethods) : tagTester('Map'); + + var isWeakMap = isIE11 ? ie11fingerprint(weakMapMethods) : tagTester('WeakMap'); + + var isSet = isIE11 ? ie11fingerprint(setMethods) : tagTester('Set'); + + var isWeakSet = tagTester('WeakSet'); + + // Retrieve the values of an object's properties. + function values(obj) { + var _keys = keys(obj); + var length = _keys.length; + var values = Array(length); + for (var i = 0; i < length; i++) { + values[i] = obj[_keys[i]]; + } + return values; + } + + // Convert an object into a list of `[key, value]` pairs. + // The opposite of `_.object` with one argument. + function pairs(obj) { + var _keys = keys(obj); + var length = _keys.length; + var pairs = Array(length); + for (var i = 0; i < length; i++) { + pairs[i] = [_keys[i], obj[_keys[i]]]; + } + return pairs; + } + + // Invert the keys and values of an object. The values must be serializable. + function invert(obj) { + var result = {}; + var _keys = keys(obj); + for (var i = 0, length = _keys.length; i < length; i++) { + result[obj[_keys[i]]] = _keys[i]; + } + return result; + } + + // Return a sorted list of the function names available on the object. + function functions(obj) { + var names = []; + for (var key in obj) { + if (isFunction$1(obj[key])) names.push(key); + } + return names.sort(); + } + + // An internal function for creating assigner functions. + function createAssigner(keysFunc, defaults) { + return function(obj) { + var length = arguments.length; + if (defaults) obj = Object(obj); + if (length < 2 || obj == null) return obj; + for (var index = 1; index < length; index++) { + var source = arguments[index], + keys = keysFunc(source), + l = keys.length; + for (var i = 0; i < l; i++) { + var key = keys[i]; + if (!defaults || obj[key] === void 0) obj[key] = source[key]; + } + } + return obj; + }; + } + + // Extend a given object with all the properties in passed-in object(s). + var extend = createAssigner(allKeys); + + // Assigns a given object with all the own properties in the passed-in + // object(s). + // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) + var extendOwn = createAssigner(keys); + + // Fill in a given object with default properties. + var defaults = createAssigner(allKeys, true); + + // Create a naked function reference for surrogate-prototype-swapping. + function ctor() { + return function(){}; + } + + // An internal function for creating a new object that inherits from another. + function baseCreate(prototype) { + if (!isObject(prototype)) return {}; + if (nativeCreate) return nativeCreate(prototype); + var Ctor = ctor(); + Ctor.prototype = prototype; + var result = new Ctor; + Ctor.prototype = null; + return result; + } + + // Creates an object that inherits from the given prototype object. + // If additional properties are provided then they will be added to the + // created object. + function create(prototype, props) { + var result = baseCreate(prototype); + if (props) extendOwn(result, props); + return result; + } + + // Create a (shallow-cloned) duplicate of an object. + function clone(obj) { + if (!isObject(obj)) return obj; + return isArray(obj) ? obj.slice() : extend({}, obj); + } + + // Invokes `interceptor` with the `obj` and then returns `obj`. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + function tap(obj, interceptor) { + interceptor(obj); + return obj; + } + + // Normalize a (deep) property `path` to array. + // Like `_.iteratee`, this function can be customized. + function toPath$1(path) { + return isArray(path) ? path : [path]; + } + _$1.toPath = toPath$1; + + // Internal wrapper for `_.toPath` to enable minification. + // Similar to `cb` for `_.iteratee`. + function toPath(path) { + return _$1.toPath(path); + } + + // Internal function to obtain a nested property in `obj` along `path`. + function deepGet(obj, path) { + var length = path.length; + for (var i = 0; i < length; i++) { + if (obj == null) return void 0; + obj = obj[path[i]]; + } + return length ? obj : void 0; + } + + // Get the value of the (deep) property on `path` from `object`. + // If any property in `path` does not exist or if the value is + // `undefined`, return `defaultValue` instead. + // The `path` is normalized through `_.toPath`. + function get(object, path, defaultValue) { + var value = deepGet(object, toPath(path)); + return isUndefined(value) ? defaultValue : value; + } + + // Shortcut function for checking if an object has a given property directly on + // itself (in other words, not on a prototype). Unlike the internal `has` + // function, this public version can also traverse nested properties. + function has(obj, path) { + path = toPath(path); + var length = path.length; + for (var i = 0; i < length; i++) { + var key = path[i]; + if (!has$1(obj, key)) return false; + obj = obj[key]; + } + return !!length; + } + + // Keep the identity function around for default iteratees. + function identity(value) { + return value; + } + + // Returns a predicate for checking whether an object has a given set of + // `key:value` pairs. + function matcher(attrs) { + attrs = extendOwn({}, attrs); + return function(obj) { + return isMatch(obj, attrs); + }; + } + + // Creates a function that, when passed an object, will traverse that object’s + // properties down the given `path`, specified as an array of keys or indices. + function property(path) { + path = toPath(path); + return function(obj) { + return deepGet(obj, path); + }; + } + + // Internal function that returns an efficient (for current engines) version + // of the passed-in callback, to be repeatedly applied in other Underscore + // functions. + function optimizeCb(func, context, argCount) { + if (context === void 0) return func; + switch (argCount == null ? 3 : argCount) { + case 1: return function(value) { + return func.call(context, value); + }; + // The 2-argument case is omitted because we’re not using it. + case 3: return function(value, index, collection) { + return func.call(context, value, index, collection); + }; + case 4: return function(accumulator, value, index, collection) { + return func.call(context, accumulator, value, index, collection); + }; + } + return function() { + return func.apply(context, arguments); + }; + } + + // An internal function to generate callbacks that can be applied to each + // element in a collection, returning the desired result — either `_.identity`, + // an arbitrary callback, a property matcher, or a property accessor. + function baseIteratee(value, context, argCount) { + if (value == null) return identity; + if (isFunction$1(value)) return optimizeCb(value, context, argCount); + if (isObject(value) && !isArray(value)) return matcher(value); + return property(value); + } + + // External wrapper for our callback generator. Users may customize + // `_.iteratee` if they want additional predicate/iteratee shorthand styles. + // This abstraction hides the internal-only `argCount` argument. + function iteratee(value, context) { + return baseIteratee(value, context, Infinity); + } + _$1.iteratee = iteratee; + + // The function we call internally to generate a callback. It invokes + // `_.iteratee` if overridden, otherwise `baseIteratee`. + function cb(value, context, argCount) { + if (_$1.iteratee !== iteratee) return _$1.iteratee(value, context); + return baseIteratee(value, context, argCount); + } + + // Returns the results of applying the `iteratee` to each element of `obj`. + // In contrast to `_.map` it returns an object. + function mapObject(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var _keys = keys(obj), + length = _keys.length, + results = {}; + for (var index = 0; index < length; index++) { + var currentKey = _keys[index]; + results[currentKey] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + } + + // Predicate-generating function. Often useful outside of Underscore. + function noop(){} + + // Generates a function for a given object that returns a given property. + function propertyOf(obj) { + if (obj == null) return noop; + return function(path) { + return get(obj, path); + }; + } + + // Run a function **n** times. + function times(n, iteratee, context) { + var accum = Array(Math.max(0, n)); + iteratee = optimizeCb(iteratee, context, 1); + for (var i = 0; i < n; i++) accum[i] = iteratee(i); + return accum; + } + + // Return a random integer between `min` and `max` (inclusive). + function random(min, max) { + if (max == null) { + max = min; + min = 0; + } + return min + Math.floor(Math.random() * (max - min + 1)); + } + + // A (possibly faster) way to get the current timestamp as an integer. + var now = Date.now || function() { + return new Date().getTime(); + }; + + // Internal helper to generate functions for escaping and unescaping strings + // to/from HTML interpolation. + function createEscaper(map) { + var escaper = function(match) { + return map[match]; + }; + // Regexes for identifying a key that needs to be escaped. + var source = '(?:' + keys(map).join('|') + ')'; + var testRegexp = RegExp(source); + var replaceRegexp = RegExp(source, 'g'); + return function(string) { + string = string == null ? '' : '' + string; + return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; + }; + } + + // Internal list of HTML entities for escaping. + var escapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`' + }; + + // Function for escaping strings to HTML interpolation. + var _escape = createEscaper(escapeMap); + + // Internal list of HTML entities for unescaping. + var unescapeMap = invert(escapeMap); + + // Function for unescaping strings from HTML interpolation. + var _unescape = createEscaper(unescapeMap); + + // By default, Underscore uses ERB-style template delimiters. Change the + // following template settings to use alternative delimiters. + var templateSettings = _$1.templateSettings = { + evaluate: /<%([\s\S]+?)%>/g, + interpolate: /<%=([\s\S]+?)%>/g, + escape: /<%-([\s\S]+?)%>/g + }; + + // When customizing `_.templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g; + + function escapeChar(match) { + return '\\' + escapes[match]; + } + + // In order to prevent third-party code injection through + // `_.templateSettings.variable`, we test it against the following regular + // expression. It is intentionally a bit more liberal than just matching valid + // identifiers, but still prevents possible loopholes through defaults or + // destructuring assignment. + var bareIdentifier = /^\s*(\w|\$)+\s*$/; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + // NB: `oldSettings` only exists for backwards compatibility. + function template(text, settings, oldSettings) { + if (!settings && oldSettings) settings = oldSettings; + settings = defaults({}, settings, _$1.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset).replace(escapeRegExp, escapeChar); + index = offset + match.length; + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } else if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } else if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + + // Adobe VMs need the match returned to produce the correct offset. + return match; + }); + source += "';\n"; + + var argument = settings.variable; + if (argument) { + // Insure against third-party code injection. (CVE-2021-23358) + if (!bareIdentifier.test(argument)) throw new Error( + 'variable is not a bare identifier: ' + argument + ); + } else { + // If a variable is not specified, place data values in local scope. + source = 'with(obj||{}){\n' + source + '}\n'; + argument = 'obj'; + } + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + 'return __p;\n'; + + var render; + try { + render = new Function(argument, '_', source); + } catch (e) { + e.source = source; + throw e; + } + + var template = function(data) { + return render.call(this, data, _$1); + }; + + // Provide the compiled source as a convenience for precompilation. + template.source = 'function(' + argument + '){\n' + source + '}'; + + return template; + } + + // Traverses the children of `obj` along `path`. If a child is a function, it + // is invoked with its parent as context. Returns the value of the final + // child, or `fallback` if any child is undefined. + function result(obj, path, fallback) { + path = toPath(path); + var length = path.length; + if (!length) { + return isFunction$1(fallback) ? fallback.call(obj) : fallback; + } + for (var i = 0; i < length; i++) { + var prop = obj == null ? void 0 : obj[path[i]]; + if (prop === void 0) { + prop = fallback; + i = length; // Ensure we don't continue iterating. + } + obj = isFunction$1(prop) ? prop.call(obj) : prop; + } + return obj; + } + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + function uniqueId(prefix) { + var id = ++idCounter + ''; + return prefix ? prefix + id : id; + } + + // Start chaining a wrapped Underscore object. + function chain(obj) { + var instance = _$1(obj); + instance._chain = true; + return instance; + } + + // Internal function to execute `sourceFunc` bound to `context` with optional + // `args`. Determines whether to execute a function as a constructor or as a + // normal function. + function executeBound(sourceFunc, boundFunc, context, callingContext, args) { + if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); + var self = baseCreate(sourceFunc.prototype); + var result = sourceFunc.apply(self, args); + if (isObject(result)) return result; + return self; + } + + // Partially apply a function by creating a version that has had some of its + // arguments pre-filled, without changing its dynamic `this` context. `_` acts + // as a placeholder by default, allowing any combination of arguments to be + // pre-filled. Set `_.partial.placeholder` for a custom placeholder argument. + var partial = restArguments(function(func, boundArgs) { + var placeholder = partial.placeholder; + var bound = function() { + var position = 0, length = boundArgs.length; + var args = Array(length); + for (var i = 0; i < length; i++) { + args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i]; + } + while (position < arguments.length) args.push(arguments[position++]); + return executeBound(func, bound, this, this, args); + }; + return bound; + }); + + partial.placeholder = _$1; + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). + var bind = restArguments(function(func, context, args) { + if (!isFunction$1(func)) throw new TypeError('Bind must be called on a function'); + var bound = restArguments(function(callArgs) { + return executeBound(func, bound, context, this, args.concat(callArgs)); + }); + return bound; + }); + + // Internal helper for collection methods to determine whether a collection + // should be iterated as an array or as an object. + // Related: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength + // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094 + var isArrayLike = createSizePropertyCheck(getLength); + + // Internal implementation of a recursive `flatten` function. + function flatten$1(input, depth, strict, output) { + output = output || []; + if (!depth && depth !== 0) { + depth = Infinity; + } else if (depth <= 0) { + return output.concat(input); + } + var idx = output.length; + for (var i = 0, length = getLength(input); i < length; i++) { + var value = input[i]; + if (isArrayLike(value) && (isArray(value) || isArguments$1(value))) { + // Flatten current level of array or arguments object. + if (depth > 1) { + flatten$1(value, depth - 1, strict, output); + idx = output.length; + } else { + var j = 0, len = value.length; + while (j < len) output[idx++] = value[j++]; + } + } else if (!strict) { + output[idx++] = value; + } + } + return output; + } + + // Bind a number of an object's methods to that object. Remaining arguments + // are the method names to be bound. Useful for ensuring that all callbacks + // defined on an object belong to it. + var bindAll = restArguments(function(obj, keys) { + keys = flatten$1(keys, false, false); + var index = keys.length; + if (index < 1) throw new Error('bindAll must be passed function names'); + while (index--) { + var key = keys[index]; + obj[key] = bind(obj[key], obj); + } + return obj; + }); + + // Memoize an expensive function by storing its results. + function memoize(func, hasher) { + var memoize = function(key) { + var cache = memoize.cache; + var address = '' + (hasher ? hasher.apply(this, arguments) : key); + if (!has$1(cache, address)) cache[address] = func.apply(this, arguments); + return cache[address]; + }; + memoize.cache = {}; + return memoize; + } + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + var delay = restArguments(function(func, wait, args) { + return setTimeout(function() { + return func.apply(null, args); + }, wait); + }); + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + var defer = partial(delay, _$1, 1); + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. Normally, the throttled function will run + // as much as it can, without ever going more than once per `wait` duration; + // but if you'd like to disable the execution on the leading edge, pass + // `{leading: false}`. To disable execution on the trailing edge, ditto. + function throttle(func, wait, options) { + var timeout, context, args, result; + var previous = 0; + if (!options) options = {}; + + var later = function() { + previous = options.leading === false ? 0 : now(); + timeout = null; + result = func.apply(context, args); + if (!timeout) context = args = null; + }; + + var throttled = function() { + var _now = now(); + if (!previous && options.leading === false) previous = _now; + var remaining = wait - (_now - previous); + context = this; + args = arguments; + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + previous = _now; + result = func.apply(context, args); + if (!timeout) context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; + + throttled.cancel = function() { + clearTimeout(timeout); + previous = 0; + timeout = context = args = null; + }; + + return throttled; + } + + // When a sequence of calls of the returned function ends, the argument + // function is triggered. The end of a sequence is defined by the `wait` + // parameter. If `immediate` is passed, the argument function will be + // triggered at the beginning of the sequence instead of at the end. + function debounce(func, wait, immediate) { + var timeout, previous, args, result, context; + + var later = function() { + var passed = now() - previous; + if (wait > passed) { + timeout = setTimeout(later, wait - passed); + } else { + timeout = null; + if (!immediate) result = func.apply(context, args); + // This check is needed because `func` can recursively invoke `debounced`. + if (!timeout) args = context = null; + } + }; + + var debounced = restArguments(function(_args) { + context = this; + args = _args; + previous = now(); + if (!timeout) { + timeout = setTimeout(later, wait); + if (immediate) result = func.apply(context, args); + } + return result; + }); + + debounced.cancel = function() { + clearTimeout(timeout); + timeout = args = context = null; + }; + + return debounced; + } + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + function wrap(func, wrapper) { + return partial(wrapper, func); + } + + // Returns a negated version of the passed-in predicate. + function negate(predicate) { + return function() { + return !predicate.apply(this, arguments); + }; + } + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + function compose() { + var args = arguments; + var start = args.length - 1; + return function() { + var i = start; + var result = args[start].apply(this, arguments); + while (i--) result = args[i].call(this, result); + return result; + }; + } + + // Returns a function that will only be executed on and after the Nth call. + function after(times, func) { + return function() { + if (--times < 1) { + return func.apply(this, arguments); + } + }; + } + + // Returns a function that will only be executed up to (but not including) the + // Nth call. + function before(times, func) { + var memo; + return function() { + if (--times > 0) { + memo = func.apply(this, arguments); + } + if (times <= 1) func = null; + return memo; + }; + } + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + var once = partial(before, 2); + + // Returns the first key on an object that passes a truth test. + function findKey(obj, predicate, context) { + predicate = cb(predicate, context); + var _keys = keys(obj), key; + for (var i = 0, length = _keys.length; i < length; i++) { + key = _keys[i]; + if (predicate(obj[key], key, obj)) return key; + } + } + + // Internal function to generate `_.findIndex` and `_.findLastIndex`. + function createPredicateIndexFinder(dir) { + return function(array, predicate, context) { + predicate = cb(predicate, context); + var length = getLength(array); + var index = dir > 0 ? 0 : length - 1; + for (; index >= 0 && index < length; index += dir) { + if (predicate(array[index], index, array)) return index; + } + return -1; + }; + } + + // Returns the first index on an array-like that passes a truth test. + var findIndex = createPredicateIndexFinder(1); + + // Returns the last index on an array-like that passes a truth test. + var findLastIndex = createPredicateIndexFinder(-1); + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + function sortedIndex(array, obj, iteratee, context) { + iteratee = cb(iteratee, context, 1); + var value = iteratee(obj); + var low = 0, high = getLength(array); + while (low < high) { + var mid = Math.floor((low + high) / 2); + if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; + } + return low; + } + + // Internal function to generate the `_.indexOf` and `_.lastIndexOf` functions. + function createIndexFinder(dir, predicateFind, sortedIndex) { + return function(array, item, idx) { + var i = 0, length = getLength(array); + if (typeof idx == 'number') { + if (dir > 0) { + i = idx >= 0 ? idx : Math.max(idx + length, i); + } else { + length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; + } + } else if (sortedIndex && idx && length) { + idx = sortedIndex(array, item); + return array[idx] === item ? idx : -1; + } + if (item !== item) { + idx = predicateFind(slice.call(array, i, length), isNaN$1); + return idx >= 0 ? idx + i : -1; + } + for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { + if (array[idx] === item) return idx; + } + return -1; + }; + } + + // Return the position of the first occurrence of an item in an array, + // or -1 if the item is not included in the array. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + var indexOf = createIndexFinder(1, findIndex, sortedIndex); + + // Return the position of the last occurrence of an item in an array, + // or -1 if the item is not included in the array. + var lastIndexOf = createIndexFinder(-1, findLastIndex); + + // Return the first value which passes a truth test. + function find(obj, predicate, context) { + var keyFinder = isArrayLike(obj) ? findIndex : findKey; + var key = keyFinder(obj, predicate, context); + if (key !== void 0 && key !== -1) return obj[key]; + } + + // Convenience version of a common use case of `_.find`: getting the first + // object containing specific `key:value` pairs. + function findWhere(obj, attrs) { + return find(obj, matcher(attrs)); + } + + // The cornerstone for collection functions, an `each` + // implementation, aka `forEach`. + // Handles raw objects in addition to array-likes. Treats all + // sparse array-likes as if they were dense. + function each(obj, iteratee, context) { + iteratee = optimizeCb(iteratee, context); + var i, length; + if (isArrayLike(obj)) { + for (i = 0, length = obj.length; i < length; i++) { + iteratee(obj[i], i, obj); + } + } else { + var _keys = keys(obj); + for (i = 0, length = _keys.length; i < length; i++) { + iteratee(obj[_keys[i]], _keys[i], obj); + } + } + return obj; + } + + // Return the results of applying the iteratee to each element. + function map(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var _keys = !isArrayLike(obj) && keys(obj), + length = (_keys || obj).length, + results = Array(length); + for (var index = 0; index < length; index++) { + var currentKey = _keys ? _keys[index] : index; + results[index] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + } + + // Internal helper to create a reducing function, iterating left or right. + function createReduce(dir) { + // Wrap code that reassigns argument variables in a separate function than + // the one that accesses `arguments.length` to avoid a perf hit. (#1991) + var reducer = function(obj, iteratee, memo, initial) { + var _keys = !isArrayLike(obj) && keys(obj), + length = (_keys || obj).length, + index = dir > 0 ? 0 : length - 1; + if (!initial) { + memo = obj[_keys ? _keys[index] : index]; + index += dir; + } + for (; index >= 0 && index < length; index += dir) { + var currentKey = _keys ? _keys[index] : index; + memo = iteratee(memo, obj[currentKey], currentKey, obj); + } + return memo; + }; + + return function(obj, iteratee, memo, context) { + var initial = arguments.length >= 3; + return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial); + }; + } + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. + var reduce = createReduce(1); + + // The right-associative version of reduce, also known as `foldr`. + var reduceRight = createReduce(-1); + + // Return all the elements that pass a truth test. + function filter(obj, predicate, context) { + var results = []; + predicate = cb(predicate, context); + each(obj, function(value, index, list) { + if (predicate(value, index, list)) results.push(value); + }); + return results; + } + + // Return all the elements for which a truth test fails. + function reject(obj, predicate, context) { + return filter(obj, negate(cb(predicate)), context); + } + + // Determine whether all of the elements pass a truth test. + function every(obj, predicate, context) { + predicate = cb(predicate, context); + var _keys = !isArrayLike(obj) && keys(obj), + length = (_keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = _keys ? _keys[index] : index; + if (!predicate(obj[currentKey], currentKey, obj)) return false; + } + return true; + } + + // Determine if at least one element in the object passes a truth test. + function some(obj, predicate, context) { + predicate = cb(predicate, context); + var _keys = !isArrayLike(obj) && keys(obj), + length = (_keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = _keys ? _keys[index] : index; + if (predicate(obj[currentKey], currentKey, obj)) return true; + } + return false; + } + + // Determine if the array or object contains a given item (using `===`). + function contains(obj, item, fromIndex, guard) { + if (!isArrayLike(obj)) obj = values(obj); + if (typeof fromIndex != 'number' || guard) fromIndex = 0; + return indexOf(obj, item, fromIndex) >= 0; + } + + // Invoke a method (with arguments) on every item in a collection. + var invoke = restArguments(function(obj, path, args) { + var contextPath, func; + if (isFunction$1(path)) { + func = path; + } else { + path = toPath(path); + contextPath = path.slice(0, -1); + path = path[path.length - 1]; + } + return map(obj, function(context) { + var method = func; + if (!method) { + if (contextPath && contextPath.length) { + context = deepGet(context, contextPath); + } + if (context == null) return void 0; + method = context[path]; + } + return method == null ? method : method.apply(context, args); + }); + }); + + // Convenience version of a common use case of `_.map`: fetching a property. + function pluck(obj, key) { + return map(obj, property(key)); + } + + // Convenience version of a common use case of `_.filter`: selecting only + // objects containing specific `key:value` pairs. + function where(obj, attrs) { + return filter(obj, matcher(attrs)); + } + + // Return the maximum element (or element-based computation). + function max(obj, iteratee, context) { + var result = -Infinity, lastComputed = -Infinity, + value, computed; + if (iteratee == null || typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null) { + obj = isArrayLike(obj) ? obj : values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value != null && value > result) { + result = value; + } + } + } else { + iteratee = cb(iteratee, context); + each(obj, function(v, index, list) { + computed = iteratee(v, index, list); + if (computed > lastComputed || computed === -Infinity && result === -Infinity) { + result = v; + lastComputed = computed; + } + }); + } + return result; + } + + // Return the minimum element (or element-based computation). + function min(obj, iteratee, context) { + var result = Infinity, lastComputed = Infinity, + value, computed; + if (iteratee == null || typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null) { + obj = isArrayLike(obj) ? obj : values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value != null && value < result) { + result = value; + } + } + } else { + iteratee = cb(iteratee, context); + each(obj, function(v, index, list) { + computed = iteratee(v, index, list); + if (computed < lastComputed || computed === Infinity && result === Infinity) { + result = v; + lastComputed = computed; + } + }); + } + return result; + } + + // Sample **n** random values from a collection using the modern version of the + // [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher–Yates_shuffle). + // If **n** is not specified, returns a single random element. + // The internal `guard` argument allows it to work with `_.map`. + function sample(obj, n, guard) { + if (n == null || guard) { + if (!isArrayLike(obj)) obj = values(obj); + return obj[random(obj.length - 1)]; + } + var sample = isArrayLike(obj) ? clone(obj) : values(obj); + var length = getLength(sample); + n = Math.max(Math.min(n, length), 0); + var last = length - 1; + for (var index = 0; index < n; index++) { + var rand = random(index, last); + var temp = sample[index]; + sample[index] = sample[rand]; + sample[rand] = temp; + } + return sample.slice(0, n); + } + + // Shuffle a collection. + function shuffle(obj) { + return sample(obj, Infinity); + } + + // Sort the object's values by a criterion produced by an iteratee. + function sortBy(obj, iteratee, context) { + var index = 0; + iteratee = cb(iteratee, context); + return pluck(map(obj, function(value, key, list) { + return { + value: value, + index: index++, + criteria: iteratee(value, key, list) + }; + }).sort(function(left, right) { + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index - right.index; + }), 'value'); + } + + // An internal function used for aggregate "group by" operations. + function group(behavior, partition) { + return function(obj, iteratee, context) { + var result = partition ? [[], []] : {}; + iteratee = cb(iteratee, context); + each(obj, function(value, index) { + var key = iteratee(value, index, obj); + behavior(result, value, key); + }); + return result; + }; + } + + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + var groupBy = group(function(result, value, key) { + if (has$1(result, key)) result[key].push(value); else result[key] = [value]; + }); + + // Indexes the object's values by a criterion, similar to `_.groupBy`, but for + // when you know that your index values will be unique. + var indexBy = group(function(result, value, key) { + result[key] = value; + }); + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + var countBy = group(function(result, value, key) { + if (has$1(result, key)) result[key]++; else result[key] = 1; + }); + + // Split a collection into two arrays: one whose elements all pass the given + // truth test, and one whose elements all do not pass the truth test. + var partition = group(function(result, value, pass) { + result[pass ? 0 : 1].push(value); + }, true); + + // Safely create a real, live array from anything iterable. + var reStrSymbol = /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g; + function toArray(obj) { + if (!obj) return []; + if (isArray(obj)) return slice.call(obj); + if (isString(obj)) { + // Keep surrogate pair characters together. + return obj.match(reStrSymbol); + } + if (isArrayLike(obj)) return map(obj, identity); + return values(obj); + } + + // Return the number of elements in a collection. + function size(obj) { + if (obj == null) return 0; + return isArrayLike(obj) ? obj.length : keys(obj).length; + } + + // Internal `_.pick` helper function to determine whether `key` is an enumerable + // property name of `obj`. + function keyInObj(value, key, obj) { + return key in obj; + } + + // Return a copy of the object only containing the allowed properties. + var pick = restArguments(function(obj, keys) { + var result = {}, iteratee = keys[0]; + if (obj == null) return result; + if (isFunction$1(iteratee)) { + if (keys.length > 1) iteratee = optimizeCb(iteratee, keys[1]); + keys = allKeys(obj); + } else { + iteratee = keyInObj; + keys = flatten$1(keys, false, false); + obj = Object(obj); + } + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i]; + var value = obj[key]; + if (iteratee(value, key, obj)) result[key] = value; + } + return result; + }); + + // Return a copy of the object without the disallowed properties. + var omit = restArguments(function(obj, keys) { + var iteratee = keys[0], context; + if (isFunction$1(iteratee)) { + iteratee = negate(iteratee); + if (keys.length > 1) context = keys[1]; + } else { + keys = map(flatten$1(keys, false, false), String); + iteratee = function(value, key) { + return !contains(keys, key); + }; + } + return pick(obj, iteratee, context); + }); + + // Returns everything but the last entry of the array. Especially useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. + function initial(array, n, guard) { + return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); + } + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. The **guard** check allows it to work with `_.map`. + function first(array, n, guard) { + if (array == null || array.length < 1) return n == null || guard ? void 0 : []; + if (n == null || guard) return array[0]; + return initial(array, array.length - n); + } + + // Returns everything but the first entry of the `array`. Especially useful on + // the `arguments` object. Passing an **n** will return the rest N values in the + // `array`. + function rest(array, n, guard) { + return slice.call(array, n == null || guard ? 1 : n); + } + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. + function last(array, n, guard) { + if (array == null || array.length < 1) return n == null || guard ? void 0 : []; + if (n == null || guard) return array[array.length - 1]; + return rest(array, Math.max(0, array.length - n)); + } + + // Trim out all falsy values from an array. + function compact(array) { + return filter(array, Boolean); + } + + // Flatten out an array, either recursively (by default), or up to `depth`. + // Passing `true` or `false` as `depth` means `1` or `Infinity`, respectively. + function flatten(array, depth) { + return flatten$1(array, depth, false); + } + + // Take the difference between one array and a number of other arrays. + // Only the elements present in just the first array will remain. + var difference = restArguments(function(array, rest) { + rest = flatten$1(rest, true, true); + return filter(array, function(value){ + return !contains(rest, value); + }); + }); + + // Return a version of the array that does not contain the specified value(s). + var without = restArguments(function(array, otherArrays) { + return difference(array, otherArrays); + }); + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // The faster algorithm will not work with an iteratee if the iteratee + // is not a one-to-one function, so providing an iteratee will disable + // the faster algorithm. + function uniq(array, isSorted, iteratee, context) { + if (!isBoolean(isSorted)) { + context = iteratee; + iteratee = isSorted; + isSorted = false; + } + if (iteratee != null) iteratee = cb(iteratee, context); + var result = []; + var seen = []; + for (var i = 0, length = getLength(array); i < length; i++) { + var value = array[i], + computed = iteratee ? iteratee(value, i, array) : value; + if (isSorted && !iteratee) { + if (!i || seen !== computed) result.push(value); + seen = computed; + } else if (iteratee) { + if (!contains(seen, computed)) { + seen.push(computed); + result.push(value); + } + } else if (!contains(result, value)) { + result.push(value); + } + } + return result; + } + + // Produce an array that contains the union: each distinct element from all of + // the passed-in arrays. + var union = restArguments(function(arrays) { + return uniq(flatten$1(arrays, true, true)); + }); + + // Produce an array that contains every item shared between all the + // passed-in arrays. + function intersection(array) { + var result = []; + var argsLength = arguments.length; + for (var i = 0, length = getLength(array); i < length; i++) { + var item = array[i]; + if (contains(result, item)) continue; + var j; + for (j = 1; j < argsLength; j++) { + if (!contains(arguments[j], item)) break; + } + if (j === argsLength) result.push(item); + } + return result; + } + + // Complement of zip. Unzip accepts an array of arrays and groups + // each array's elements on shared indices. + function unzip(array) { + var length = array && max(array, getLength).length || 0; + var result = Array(length); + + for (var index = 0; index < length; index++) { + result[index] = pluck(array, index); + } + return result; + } + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + var zip = restArguments(unzip); + + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. Passing by pairs is the reverse of `_.pairs`. + function object(list, values) { + var result = {}; + for (var i = 0, length = getLength(list); i < length; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } + } + return result; + } + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](https://docs.python.org/library/functions.html#range). + function range(start, stop, step) { + if (stop == null) { + stop = start || 0; + start = 0; + } + if (!step) { + step = stop < start ? -1 : 1; + } + + var length = Math.max(Math.ceil((stop - start) / step), 0); + var range = Array(length); + + for (var idx = 0; idx < length; idx++, start += step) { + range[idx] = start; + } + + return range; + } + + // Chunk a single array into multiple arrays, each containing `count` or fewer + // items. + function chunk(array, count) { + if (count == null || count < 1) return []; + var result = []; + var i = 0, length = array.length; + while (i < length) { + result.push(slice.call(array, i, i += count)); + } + return result; + } + + // Helper function to continue chaining intermediate results. + function chainResult(instance, obj) { + return instance._chain ? _$1(obj).chain() : obj; + } + + // Add your own custom functions to the Underscore object. + function mixin(obj) { + each(functions(obj), function(name) { + var func = _$1[name] = obj[name]; + _$1.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return chainResult(this, func.apply(_$1, args)); + }; + }); + return _$1; + } + + // Add all mutator `Array` functions to the wrapper. + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = ArrayProto[name]; + _$1.prototype[name] = function() { + var obj = this._wrapped; + if (obj != null) { + method.apply(obj, arguments); + if ((name === 'shift' || name === 'splice') && obj.length === 0) { + delete obj[0]; + } + } + return chainResult(this, obj); + }; + }); + + // Add all accessor `Array` functions to the wrapper. + each(['concat', 'join', 'slice'], function(name) { + var method = ArrayProto[name]; + _$1.prototype[name] = function() { + var obj = this._wrapped; + if (obj != null) obj = method.apply(obj, arguments); + return chainResult(this, obj); + }; + }); + + // Named Exports + + var allExports = { + __proto__: null, + VERSION: VERSION, + restArguments: restArguments, + isObject: isObject, + isNull: isNull, + isUndefined: isUndefined, + isBoolean: isBoolean, + isElement: isElement, + isString: isString, + isNumber: isNumber, + isDate: isDate, + isRegExp: isRegExp, + isError: isError, + isSymbol: isSymbol, + isArrayBuffer: isArrayBuffer, + isDataView: isDataView$1, + isArray: isArray, + isFunction: isFunction$1, + isArguments: isArguments$1, + isFinite: isFinite$1, + isNaN: isNaN$1, + isTypedArray: isTypedArray$1, + isEmpty: isEmpty, + isMatch: isMatch, + isEqual: isEqual, + isMap: isMap, + isWeakMap: isWeakMap, + isSet: isSet, + isWeakSet: isWeakSet, + keys: keys, + allKeys: allKeys, + values: values, + pairs: pairs, + invert: invert, + functions: functions, + methods: functions, + extend: extend, + extendOwn: extendOwn, + assign: extendOwn, + defaults: defaults, + create: create, + clone: clone, + tap: tap, + get: get, + has: has, + mapObject: mapObject, + identity: identity, + constant: constant, + noop: noop, + toPath: toPath$1, + property: property, + propertyOf: propertyOf, + matcher: matcher, + matches: matcher, + times: times, + random: random, + now: now, + escape: _escape, + unescape: _unescape, + templateSettings: templateSettings, + template: template, + result: result, + uniqueId: uniqueId, + chain: chain, + iteratee: iteratee, + partial: partial, + bind: bind, + bindAll: bindAll, + memoize: memoize, + delay: delay, + defer: defer, + throttle: throttle, + debounce: debounce, + wrap: wrap, + negate: negate, + compose: compose, + after: after, + before: before, + once: once, + findKey: findKey, + findIndex: findIndex, + findLastIndex: findLastIndex, + sortedIndex: sortedIndex, + indexOf: indexOf, + lastIndexOf: lastIndexOf, + find: find, + detect: find, + findWhere: findWhere, + each: each, + forEach: each, + map: map, + collect: map, + reduce: reduce, + foldl: reduce, + inject: reduce, + reduceRight: reduceRight, + foldr: reduceRight, + filter: filter, + select: filter, + reject: reject, + every: every, + all: every, + some: some, + any: some, + contains: contains, + includes: contains, + include: contains, + invoke: invoke, + pluck: pluck, + where: where, + max: max, + min: min, + shuffle: shuffle, + sample: sample, + sortBy: sortBy, + groupBy: groupBy, + indexBy: indexBy, + countBy: countBy, + partition: partition, + toArray: toArray, + size: size, + pick: pick, + omit: omit, + first: first, + head: first, + take: first, + initial: initial, + last: last, + rest: rest, + tail: rest, + drop: rest, + compact: compact, + flatten: flatten, + without: without, + uniq: uniq, + unique: uniq, + union: union, + intersection: intersection, + difference: difference, + unzip: unzip, + transpose: unzip, + zip: zip, + object: object, + range: range, + chunk: chunk, + mixin: mixin, + 'default': _$1 + }; + + // Default Export + + // Add all of the Underscore functions to the wrapper object. + var _ = mixin(allExports); + // Legacy Node.js API. + _._ = _; + + return _; + +}))); +//# sourceMappingURL=underscore-umd.js.map diff --git a/docs/_static/underscore.js b/docs/_static/underscore.js index 166240ef..cf177d42 100644 --- a/docs/_static/underscore.js +++ b/docs/_static/underscore.js @@ -1,6 +1,6 @@ -!function(n,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define("underscore",r):(n=n||self,function(){var t=n._,e=n._=r();e.noConflict=function(){return n._=t,e}}())}(this,(function(){ -// Underscore.js 1.12.0 +!function(n,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define("underscore",r):(n="undefined"!=typeof globalThis?globalThis:n||self,function(){var t=n._,e=n._=r();e.noConflict=function(){return n._=t,e}}())}(this,(function(){ +// Underscore.js 1.13.1 // https://underscorejs.org -// (c) 2009-2020 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// (c) 2009-2021 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and Investigative Reporters & Editors // Underscore may be freely distributed under the MIT license. -var n="1.12.0",r="object"==typeof self&&self.self===self&&self||"object"==typeof global&&global.global===global&&global||Function("return this")()||{},t=Array.prototype,e=Object.prototype,u="undefined"!=typeof Symbol?Symbol.prototype:null,o=t.push,i=t.slice,a=e.toString,f=e.hasOwnProperty,c="undefined"!=typeof ArrayBuffer,l="undefined"!=typeof DataView,s=Array.isArray,p=Object.keys,v=Object.create,h=c&&ArrayBuffer.isView,y=isNaN,g=isFinite,d=!{toString:null}.propertyIsEnumerable("toString"),b=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"],m=Math.pow(2,53)-1;function j(n,r){return r=null==r?n.length-1:+r,function(){for(var t=Math.max(arguments.length-r,0),e=Array(t),u=0;u=0&&t<=m}}function $(n){return function(r){return null==r?void 0:r[n]}}var G=$("byteLength"),H=J(G),Q=/\[object ((I|Ui)nt(8|16|32)|Float(32|64)|Uint8Clamped|Big(I|Ui)nt64)Array\]/;var X=c?function(n){return h?h(n)&&!q(n):H(n)&&Q.test(a.call(n))}:K(!1),Y=$("length");function Z(n,r){r=function(n){for(var r={},t=n.length,e=0;e":">",'"':""","'":"'","`":"`"},Kn=Ln(Cn),Jn=Ln(_n(Cn)),$n=tn.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g},Gn=/(.)^/,Hn={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},Qn=/\\|'|\r|\n|\u2028|\u2029/g;function Xn(n){return"\\"+Hn[n]}var Yn=0;function Zn(n,r,t,e,u){if(!(e instanceof r))return n.apply(t,u);var o=Mn(n.prototype),i=n.apply(o,u);return _(i)?i:o}var nr=j((function(n,r){var t=nr.placeholder,e=function(){for(var u=0,o=r.length,i=Array(o),a=0;a1)er(a,r-1,t,e),u=e.length;else for(var f=0,c=a.length;f0&&(t=r.apply(this,arguments)),n<=1&&(r=null),t}}var cr=nr(fr,2);function lr(n,r,t){r=qn(r,t);for(var e,u=nn(n),o=0,i=u.length;o0?0:u-1;o>=0&&o0?a=o>=0?o:Math.max(o+f,a):f=o>=0?Math.min(o+1,f):o+f+1;else if(t&&o&&f)return e[o=t(e,u)]===u?o:-1;if(u!=u)return(o=r(i.call(e,a,f),C))>=0?o+a:-1;for(o=n>0?a:f-1;o>=0&&o0?0:i-1;for(u||(e=r[o?o[a]:a],a+=n);a>=0&&a=3;return r(n,Fn(t,u,4),e,o)}}var wr=_r(1),Ar=_r(-1);function xr(n,r,t){var e=[];return r=qn(r,t),mr(n,(function(n,t,u){r(n,t,u)&&e.push(n)})),e}function Sr(n,r,t){r=qn(r,t);for(var e=!tr(n)&&nn(n),u=(e||n).length,o=0;o=0}var Er=j((function(n,r,t){var e,u;return D(r)?u=r:(r=Nn(r),e=r.slice(0,-1),r=r[r.length-1]),jr(n,(function(n){var o=u;if(!o){if(e&&e.length&&(n=In(n,e)),null==n)return;o=n[r]}return null==o?o:o.apply(n,t)}))}));function Br(n,r){return jr(n,Rn(r))}function Nr(n,r,t){var e,u,o=-1/0,i=-1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var a=0,f=(n=tr(n)?n:jn(n)).length;ao&&(o=e);else r=qn(r,t),mr(n,(function(n,t,e){((u=r(n,t,e))>i||u===-1/0&&o===-1/0)&&(o=n,i=u)}));return o}function Ir(n,r,t){if(null==r||t)return tr(n)||(n=jn(n)),n[Wn(n.length-1)];var e=tr(n)?En(n):jn(n),u=Y(e);r=Math.max(Math.min(r,u),0);for(var o=u-1,i=0;i1&&(e=Fn(e,r[1])),r=an(n)):(e=Pr,r=er(r,!1,!1),n=Object(n));for(var u=0,o=r.length;u1&&(t=r[1])):(r=jr(er(r,!1,!1),String),e=function(n,t){return!Mr(r,t)}),qr(n,e,t)}));function Wr(n,r,t){return i.call(n,0,Math.max(0,n.length-(null==r||t?1:r)))}function zr(n,r,t){return null==n||n.length<1?null==r||t?void 0:[]:null==r||t?n[0]:Wr(n,n.length-r)}function Lr(n,r,t){return i.call(n,null==r||t?1:r)}var Cr=j((function(n,r){return r=er(r,!0,!0),xr(n,(function(n){return!Mr(r,n)}))})),Kr=j((function(n,r){return Cr(n,r)}));function Jr(n,r,t,e){A(r)||(e=t,t=r,r=!1),null!=t&&(t=qn(t,e));for(var u=[],o=[],i=0,a=Y(n);ir?(e&&(clearTimeout(e),e=null),a=c,i=n.apply(u,o),e||(u=o=null)):e||!1===t.trailing||(e=setTimeout(f,l)),i};return c.cancel=function(){clearTimeout(e),a=0,e=u=o=null},c},debounce:function(n,r,t){var e,u,o=function(r,t){e=null,t&&(u=n.apply(r,t))},i=j((function(i){if(e&&clearTimeout(e),t){var a=!e;e=setTimeout(o,r),a&&(u=n.apply(this,i))}else e=or(o,r,this,i);return u}));return i.cancel=function(){clearTimeout(e),e=null},i},wrap:function(n,r){return nr(r,n)},negate:ar,compose:function(){var n=arguments,r=n.length-1;return function(){for(var t=r,e=n[r].apply(this,arguments);t--;)e=n[t].call(this,e);return e}},after:function(n,r){return function(){if(--n<1)return r.apply(this,arguments)}},before:fr,once:cr,findKey:lr,findIndex:pr,findLastIndex:vr,sortedIndex:hr,indexOf:gr,lastIndexOf:dr,find:br,detect:br,findWhere:function(n,r){return br(n,Dn(r))},each:mr,forEach:mr,map:jr,collect:jr,reduce:wr,foldl:wr,inject:wr,reduceRight:Ar,foldr:Ar,filter:xr,select:xr,reject:function(n,r,t){return xr(n,ar(qn(r)),t)},every:Sr,all:Sr,some:Or,any:Or,contains:Mr,includes:Mr,include:Mr,invoke:Er,pluck:Br,where:function(n,r){return xr(n,Dn(r))},max:Nr,min:function(n,r,t){var e,u,o=1/0,i=1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var a=0,f=(n=tr(n)?n:jn(n)).length;ae||void 0===t)return 1;if(t=0&&t<=m}}function J(n){return function(r){return null==r?void 0:r[n]}}var G=J("byteLength"),H=K(G),Q=/\[object ((I|Ui)nt(8|16|32)|Float(32|64)|Uint8Clamped|Big(I|Ui)nt64)Array\]/;var X=c?function(n){return h?h(n)&&!q(n):H(n)&&Q.test(a.call(n))}:C(!1),Y=J("length");function Z(n,r){r=function(n){for(var r={},t=n.length,e=0;e":">",'"':""","'":"'","`":"`"},Cn=Ln($n),Kn=Ln(_n($n)),Jn=tn.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g},Gn=/(.)^/,Hn={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},Qn=/\\|'|\r|\n|\u2028|\u2029/g;function Xn(n){return"\\"+Hn[n]}var Yn=/^\s*(\w|\$)+\s*$/;var Zn=0;function nr(n,r,t,e,u){if(!(e instanceof r))return n.apply(t,u);var o=Mn(n.prototype),i=n.apply(o,u);return _(i)?i:o}var rr=j((function(n,r){var t=rr.placeholder,e=function(){for(var u=0,o=r.length,i=Array(o),a=0;a1)ur(a,r-1,t,e),u=e.length;else for(var f=0,c=a.length;f0&&(t=r.apply(this,arguments)),n<=1&&(r=null),t}}var lr=rr(cr,2);function sr(n,r,t){r=qn(r,t);for(var e,u=nn(n),o=0,i=u.length;o0?0:u-1;o>=0&&o0?a=o>=0?o:Math.max(o+f,a):f=o>=0?Math.min(o+1,f):o+f+1;else if(t&&o&&f)return e[o=t(e,u)]===u?o:-1;if(u!=u)return(o=r(i.call(e,a,f),$))>=0?o+a:-1;for(o=n>0?a:f-1;o>=0&&o0?0:i-1;for(u||(e=r[o?o[a]:a],a+=n);a>=0&&a=3;return r(n,Fn(t,u,4),e,o)}}var Ar=wr(1),xr=wr(-1);function Sr(n,r,t){var e=[];return r=qn(r,t),jr(n,(function(n,t,u){r(n,t,u)&&e.push(n)})),e}function Or(n,r,t){r=qn(r,t);for(var e=!er(n)&&nn(n),u=(e||n).length,o=0;o=0}var Br=j((function(n,r,t){var e,u;return D(r)?u=r:(r=Nn(r),e=r.slice(0,-1),r=r[r.length-1]),_r(n,(function(n){var o=u;if(!o){if(e&&e.length&&(n=In(n,e)),null==n)return;o=n[r]}return null==o?o:o.apply(n,t)}))}));function Nr(n,r){return _r(n,Rn(r))}function Ir(n,r,t){var e,u,o=-1/0,i=-1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var a=0,f=(n=er(n)?n:jn(n)).length;ao&&(o=e);else r=qn(r,t),jr(n,(function(n,t,e){((u=r(n,t,e))>i||u===-1/0&&o===-1/0)&&(o=n,i=u)}));return o}function Tr(n,r,t){if(null==r||t)return er(n)||(n=jn(n)),n[Wn(n.length-1)];var e=er(n)?En(n):jn(n),u=Y(e);r=Math.max(Math.min(r,u),0);for(var o=u-1,i=0;i1&&(e=Fn(e,r[1])),r=an(n)):(e=qr,r=ur(r,!1,!1),n=Object(n));for(var u=0,o=r.length;u1&&(t=r[1])):(r=_r(ur(r,!1,!1),String),e=function(n,t){return!Er(r,t)}),Ur(n,e,t)}));function zr(n,r,t){return i.call(n,0,Math.max(0,n.length-(null==r||t?1:r)))}function Lr(n,r,t){return null==n||n.length<1?null==r||t?void 0:[]:null==r||t?n[0]:zr(n,n.length-r)}function $r(n,r,t){return i.call(n,null==r||t?1:r)}var Cr=j((function(n,r){return r=ur(r,!0,!0),Sr(n,(function(n){return!Er(r,n)}))})),Kr=j((function(n,r){return Cr(n,r)}));function Jr(n,r,t,e){A(r)||(e=t,t=r,r=!1),null!=t&&(t=qn(t,e));for(var u=[],o=[],i=0,a=Y(n);ir?(e&&(clearTimeout(e),e=null),a=c,i=n.apply(u,o),e||(u=o=null)):e||!1===t.trailing||(e=setTimeout(f,l)),i};return c.cancel=function(){clearTimeout(e),a=0,e=u=o=null},c},debounce:function(n,r,t){var e,u,o,i,a,f=function(){var c=zn()-u;r>c?e=setTimeout(f,r-c):(e=null,t||(i=n.apply(a,o)),e||(o=a=null))},c=j((function(c){return a=this,o=c,u=zn(),e||(e=setTimeout(f,r),t&&(i=n.apply(a,o))),i}));return c.cancel=function(){clearTimeout(e),e=o=a=null},c},wrap:function(n,r){return rr(r,n)},negate:fr,compose:function(){var n=arguments,r=n.length-1;return function(){for(var t=r,e=n[r].apply(this,arguments);t--;)e=n[t].call(this,e);return e}},after:function(n,r){return function(){if(--n<1)return r.apply(this,arguments)}},before:cr,once:lr,findKey:sr,findIndex:vr,findLastIndex:hr,sortedIndex:yr,indexOf:gr,lastIndexOf:br,find:mr,detect:mr,findWhere:function(n,r){return mr(n,Dn(r))},each:jr,forEach:jr,map:_r,collect:_r,reduce:Ar,foldl:Ar,inject:Ar,reduceRight:xr,foldr:xr,filter:Sr,select:Sr,reject:function(n,r,t){return Sr(n,fr(qn(r)),t)},every:Or,all:Or,some:Mr,any:Mr,contains:Er,includes:Er,include:Er,invoke:Br,pluck:Nr,where:function(n,r){return Sr(n,Dn(r))},max:Ir,min:function(n,r,t){var e,u,o=1/0,i=1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var a=0,f=(n=er(n)?n:jn(n)).length;ae||void 0===t)return 1;if(t - osxphotos command line interface (CLI) — osxphotos 0.42.20 documentation - - - + osxphotos command line interface (CLI) — osxphotos 0.42.66 documentation + + + @@ -41,20 +41,20 @@

Options

-
---db <Photos database path>
+
+--db <Photos database path>

Specify Photos database path. Path to Photos library/database can be specified using either –db or directly as PHOTOS_LIBRARY positional argument. If neither –db or PHOTOS_LIBRARY provided, will attempt to find the library to use in the following order: 1. last opened library, 2. system library, 3. ~/Pictures/Photos Library.photoslibrary

-
---json
+
+--json

Print output in JSON format.

-
--v, --version
+
+-v, --version

Show the version and exit.

@@ -73,21 +73,21 @@

Options

-
---db <Photos database path>
+
+--db <Photos database path>

Specify Photos database path. Path to Photos library/database can be specified using either –db or directly as PHOTOS_LIBRARY positional argument. If neither –db or PHOTOS_LIBRARY provided, will attempt to find the library to use in the following order: 1. last opened library, 2. system library, 3. ~/Pictures/Photos Library.photoslibrary

-
---json
+
+--json

Print output in JSON format.

Arguments

-
-PHOTOS_LIBRARY
+
+PHOTOS_LIBRARY

Optional argument(s)

@@ -100,33 +100,33 @@

Options

-
---db <Photos database path>
+
+--db <Photos database path>

Specify Photos database path. Path to Photos library/database can be specified using either –db or directly as PHOTOS_LIBRARY positional argument. If neither –db or PHOTOS_LIBRARY provided, will attempt to find the library to use in the following order: 1. last opened library, 2. system library, 3. ~/Pictures/Photos Library.photoslibrary

-
---json
+
+--json

Print output in JSON format.

-
---deleted
+
+--deleted

Include photos from the ‘Recently Deleted’ folder.

-
---deleted-only
+
+--deleted-only

Include only photos from the ‘Recently Deleted’ folder.

Arguments

-
-PHOTOS_LIBRARY
+
+PHOTOS_LIBRARY

Optional argument(s)

@@ -148,506 +148,554 @@ to modify this behavior.

Options

-
---db <Photos database path>
+
+--db <Photos database path>

Specify Photos database path. Path to Photos library/database can be specified using either –db or directly as PHOTOS_LIBRARY positional argument. If neither –db or PHOTOS_LIBRARY provided, will attempt to find the library to use in the following order: 1. last opened library, 2. system library, 3. ~/Pictures/Photos Library.photoslibrary

-
--V, --verbose
+
+-V, --verbose

Print verbose output.

-
---keyword <KEYWORD>
+
+--keyword <KEYWORD>

Search for photos with keyword KEYWORD. If more than one keyword, treated as “OR”, e.g. find photos matching any keyword

-
---person <PERSON>
+
+--person <PERSON>

Search for photos with person PERSON. If more than one person, treated as “OR”, e.g. find photos matching any person

-
---album <ALBUM>
+
+--album <ALBUM>

Search for photos in album ALBUM. If more than one album, treated as “OR”, e.g. find photos matching any album

-
---folder <FOLDER>
+
+--folder <FOLDER>

Search for photos in an album in folder FOLDER. If more than one folder, treated as “OR”, e.g. find photos in any FOLDER. Only searches top level folders (e.g. does not look at subfolders)

-
---name <FILENAME>
+
+--name <FILENAME>

Search for photos with filename matching FILENAME. If more than one –name options is specified, they are treated as “OR”, e.g. find photos matching any FILENAME.

-
---uuid <UUID>
+
+--uuid <UUID>

Search for photos with UUID(s).

-
---uuid-from-file <FILE>
+
+--uuid-from-file <FILE>

Search for photos with UUID(s) loaded from FILE. Format is a single UUID per line. Lines preceded with # are ignored.

-
---title <TITLE>
+
+--title <TITLE>

Search for TITLE in title of photo.

-
---no-title
+
+--no-title

Search for photos with no title.

-
---description <DESC>
+
+--description <DESC>

Search for DESC in description of photo.

-
---no-description
+
+--no-description

Search for photos with no description.

-
---place <PLACE>
+
+--place <PLACE>

Search for PLACE in photo’s reverse geolocation info

-
---no-place
+
+--no-place

Search for photos with no associated place name info (no reverse geolocation info)

-
---label <LABEL>
+
+--location
+

Search for photos with associated location info (e.g. GPS coordinates)

+
+ +
+
+--no-location
+

Search for photos with no associated location info (e.g. no GPS coordinates)

+
+ +
+
+--label <LABEL>

Search for photos with image classification label LABEL (Photos 5 only). If more than one label, treated as “OR”, e.g. find photos matching any label

-
---uti <UTI>
+
+--uti <UTI>

Search for photos whose uniform type identifier (UTI) matches UTI

-
--i, --ignore-case
+
+-i, --ignore-case

Case insensitive search for title, description, place, keyword, person, or album.

-
---edited
+
+--edited

Search for photos that have been edited.

-
---external-edit
+
+--external-edit

Search for photos edited in external editor.

-
---favorite
+
+--favorite

Search for photos marked favorite.

-
---not-favorite
+
+--not-favorite

Search for photos not marked favorite.

-
---hidden
+
+--hidden

Search for photos marked hidden.

-
---not-hidden
+
+--not-hidden

Search for photos not marked hidden.

-
---shared
+
+--shared

Search for photos in shared iCloud album (Photos 5 only).

-
---not-shared
+
+--not-shared

Search for photos not in shared iCloud album (Photos 5 only).

-
---burst
+
+--burst

Search for photos that were taken in a burst.

-
---not-burst
+
+--not-burst

Search for photos that are not part of a burst.

-
---live
+
+--live

Search for Apple live photos

-
---not-live
+
+--not-live

Search for photos that are not Apple live photos.

-
---portrait
+
+--portrait

Search for Apple portrait mode photos.

-
---not-portrait
+
+--not-portrait

Search for photos that are not Apple portrait mode photos.

-
---screenshot
+
+--screenshot

Search for screenshot photos.

-
---not-screenshot
+
+--not-screenshot

Search for photos that are not screenshot photos.

-
---slow-mo
+
+--slow-mo

Search for slow motion videos.

-
---not-slow-mo
+
+--not-slow-mo

Search for photos that are not slow motion videos.

-
---time-lapse
+
+--time-lapse

Search for time lapse videos.

-
---not-time-lapse
+
+--not-time-lapse

Search for photos that are not time lapse videos.

-
---hdr
+
+--hdr

Search for high dynamic range (HDR) photos.

-
---not-hdr
+
+--not-hdr

Search for photos that are not HDR photos.

-
---selfie
+
+--selfie

Search for selfies (photos taken with front-facing cameras).

-
---not-selfie
+
+--not-selfie

Search for photos that are not selfies.

-
---panorama
+
+--panorama

Search for panorama photos.

-
---not-panorama
+
+--not-panorama

Search for photos that are not panoramas.

-
---has-raw
+
+--has-raw

Search for photos with both a jpeg and raw version

-
---only-movies
+
+--only-movies

Search only for movies (default searches both images and movies).

-
---only-photos
+
+--only-photos

Search only for photos/images (default searches both images and movies).

-
---from-date <from_date>
+
+--from-date <from_date>

Search by item start date, e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601 with/without timezone).

-
---to-date <to_date>
+
+--to-date <to_date>

Search by item end date, e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601 with/without timezone).

-
---from-time <from_time>
+
+--from-time <from_time>

Search by item start time of day, e.g. 12:00, or 12:00:00.

-
---to-time <to_time>
+
+--to-time <to_time>

Search by item end time of day, e.g. 12:00 or 12:00:00.

-
---has-comment
+
+--has-comment

Search for photos that have comments.

-
---no-comment
+
+--no-comment

Search for photos with no comments.

-
---has-likes
+
+--has-likes

Search for photos that have likes.

-
---no-likes
+
+--no-likes

Search for photos with no likes.

-
---is-reference
+
+--is-reference

Search for photos that were imported as referenced files (not copied into Photos library).

-
---in-album
+
+--in-album

Search for photos that are in one or more albums.

-
---not-in-album
+
+--not-in-album

Search for photos that are not in any albums.

-
---min-size <SIZE>
+
+--duplicate
+

Search for photos with possible duplicates. osxphotos will compare signatures of photos, evaluating date created, size, height, width, and edited status to find possible duplicates. This does not compare images byte-for-byte nor compare hashes but should find photos imported multiple times or duplicated within Photos.

+
+ +
+
+--min-size <SIZE>

Search for photos with size >= SIZE bytes. The size evaluated is the photo’s original size (when imported to Photos). Size may be specified as integer bytes or using SI or NIST units. For example, the following are all valid and equivalent sizes: ‘1048576’ ‘1.048576MB’, ‘1 MiB’.

-
---max-size <SIZE>
+
+--max-size <SIZE>

Search for photos with size <= SIZE bytes. The size evaluated is the photo’s original size (when imported to Photos). Size may be specified as integer bytes or using SI or NIST units. For example, the following are all valid and equivalent sizes: ‘1048576’ ‘1.048576MB’, ‘1 MiB’.

-
---regex <REGEX TEMPLATE>
+
+--regex <REGEX TEMPLATE>

Search for photos where TEMPLATE matches regular expression REGEX. For example, to find photos in an album that begins with ‘Beach’: ‘–regex “^Beach” “{album}”’. You may specify more than one regular expression match by repeating ‘–regex’ with different arguments.

-
---query-eval <CRITERIA>
+
+--selected
+

Filter for photos that are currently selected in Photos.

+
+ +
+
+--query-eval <CRITERIA>

Evaluate CRITERIA to filter photos. CRITERIA will be evaluated in context of the following python list comprehension: photos = [photo for photo in photos if CRITERIA] where photo represents a PhotoInfo object. For example: –query-eval photo.favorite returns all photos that have been favorited and is equivalent to –favorite. You may specify more than one CRITERIA by using –query-eval multiple times. CRITERIA must be a valid python expression. See https://rhettbull.github.io/osxphotos/ for additional documentation on the PhotoInfo class.

-
---missing
+
+--query-function <filename.py::function>
+

Run function to filter photos. Use this in format: –query-function filename.py::function where filename.py is a python file you’ve created and function is the name of the function in the python file you want to call. Your function will be passed a list of PhotoInfo objects and is expected to return a filtered list of PhotoInfo objects. You may use more than one function by repeating the –query-function option with a different value. Your query function will be called after all other query options have been evaluated. See https://github.com/RhetTbull/osxphotos/blob/master/examples/query_function.py for example of how to use this option.

+
+ +
+
+--missing

Export only photos missing from the Photos library; must be used with –download-missing.

-
---deleted
+
+--deleted

Include photos from the ‘Recently Deleted’ folder.

-
---deleted-only
+
+--deleted-only

Include only photos from the ‘Recently Deleted’ folder.

-
---update
+
+--update

Only export new or updated files. See notes below on export and –update.

-
---ignore-signature
+
+--ignore-signature

When used with ‘–update’, ignores file signature when updating files. This is useful if you have processed or edited exported photos changing the file signature (size & modification date). In this case, ‘–update’ would normally re-export the processed files but with ‘–ignore-signature’, files which exist in the export directory will not be re-exported. If used with ‘–sidecar’, ‘–ignore-signature’ has the following behavior: 1) if the metadata (in Photos) that went into the sidecar did not change, the sidecar will not be updated; 2) if the metadata (in Photos) that went into the sidecar did change, a new sidecar is written but a new image file is not; 3) if a sidecar does not exist for the photo, a sidecar will be written whether or not the photo file was written or updated.

-
---only-new
+
+--only-new

If used with –update, ignores any previously exported files, even if missing from the export folder and only exports new files that haven’t previously been exported.

-
---dry-run
+
+--dry-run

Dry run (test) the export but don’t actually export any files; most useful with –verbose.

- +

Hardlink files instead of copying them. Cannot be used with –exiftool which creates copies of the files with embedded EXIF data. Note: on APFS volumes, files are cloned when exporting giving many of the same advantages as hardlinks without having to use –export-as-hardlink.

-
---touch-file
+
+--touch-file

Sets the file’s modification time to match photo date.

-
---overwrite
+
+--overwrite

Overwrite existing files. Default behavior is to add (1), (2), etc to filename if file already exists. Use this with caution as it may create name collisions on export. (e.g. if two files happen to have the same name)

-
---retry <RETRY>
+
+--retry <RETRY>

Automatically retry export up to RETRY times if an error occurs during export. This may be useful with network drives that experience intermittent errors.

-
---export-by-date
+
+--export-by-date

Automatically create output folders to organize photos by date created (e.g. DEST/2019/12/20/photoname.jpg).

-
---skip-edited
+
+--skip-edited

Do not export edited version of photo if an edited version exists.

-
---skip-original-if-edited
+
+--skip-original-if-edited

Do not export original if there is an edited version (exports only the edited version).

-
---skip-bursts
+
+--skip-bursts

Do not export all associated burst images in the library if a photo is a burst photo.

-
---skip-live
+
+--skip-live

Do not export the associated live video component of a live photo.

-
---skip-raw
-

Do not export associated raw images of a RAW+JPEG pair. Note: this does not skip raw photos if the raw photo does not have an associated jpeg image (e.g. the raw file was imported to Photos without a jpeg preview).

+
+--skip-raw
+

Do not export associated RAW image of a RAW+JPEG pair. Note: this does not skip RAW photos if the RAW photo does not have an associated JPEG image (e.g. the RAW file was imported to Photos without a JPEG preview).

-
---current-name
+
+--current-name

Use photo’s current filename instead of original filename for export. Note: Starting with Photos 5, all photos are renamed upon import. By default, photos are exported with the the original name they had before import.

-
---convert-to-jpeg
-

Convert all non-jpeg images (e.g. raw, HEIC, PNG, etc) to JPEG upon export. Only works if your Mac has a GPU.

+
+--convert-to-jpeg
+

Convert all non-JPEG images (e.g. RAW, HEIC, PNG, etc) to JPEG upon export. Note: does not convert the RAW component of a RAW+JPEG pair as the associated JPEG image will be exported. You can use –skip-raw to skip exporting the associated RAW image of a RAW+JPEG pair. See also –jpeg-quality and –jpeg-ext. Only works if your Mac has a GPU (thus may not work on virtual machines).

-
---jpeg-quality <jpeg_quality>
+
+--jpeg-quality <jpeg_quality>

Value in range 0.0 to 1.0 to use with –convert-to-jpeg. A value of 1.0 specifies best quality, a value of 0.0 specifies maximum compression. Defaults to 1.0

-
---download-missing
+
+--preview
+

Export preview image generated by Photos. This is a lower-resolution image used by Photos to quickly preview the image. See also –preview-suffix and –preview-if-missing.

+
+ +
+
+--preview-if-missing
+

Export preview image generated by Photos if the actual photo file is missing from the library. This may be helpful if photos were not copied to the Photos library and the original photo is missing. See also –preview-suffix and –preview.

+
+ +
+
+--preview-suffix <SUFFIX>
+

Optional suffix template for naming preview photos. Default name for preview photos is in form ‘photoname_preview.ext’. For example, with ‘–preview-suffix _low_res’, the preview photo would be named ‘photoname_low_res.ext’. The default suffix is ‘_preview’. Multi-value templates (see Templating System) are not permitted with –preview-suffix. See also –preview and –preview-if-missing.

+
+ +
+
+--download-missing

Attempt to download missing photos from iCloud. The current implementation uses Applescript to interact with Photos to export the photo which will force Photos to download from iCloud if the photo does not exist on disk. This will be slow and will require internet connection. This obviously only works if the Photos library is synched to iCloud. Note: –download-missing does not currently export all burst images; only the primary photo will be exported–associated burst images will be skipped.

-
---sidecar <FORMAT>
+
+--sidecar <FORMAT>

Create sidecar for each photo exported; valid FORMAT values: xmp, json, exiftool; –sidecar xmp: create XMP sidecar used by Digikam, Adobe Lightroom, etc. The sidecar file is named in format photoname.ext.xmp The XMP sidecar exports the following tags: Description, Title, Keywords/Tags, Subject (set to Keywords + PersonInImage), PersonInImage, CreateDate, ModifyDate, GPSLongitude, Face Regions (Metadata Working Group and Microsoft Photo). –sidecar json: create JSON sidecar useable by exiftool (https://exiftool.org/) The sidecar file can be used to apply metadata to the file with exiftool, for example: “exiftool -j=photoname.jpg.json photoname.jpg” The sidecar file is named in format photoname.ext.json; format includes tag groups (equivalent to running ‘exiftool -G -j’). –sidecar exiftool: create JSON sidecar compatible with output of ‘exiftool -j’. Unlike ‘–sidecar json’, ‘–sidecar exiftool’ does not export tag groups. Sidecar filename is in format photoname.ext.json; For a list of tags exported in the JSON and exiftool sidecar, see ‘–exiftool’. See also ‘–ignore-signature’.

@@ -659,110 +707,110 @@ to modify this behavior.

-
---sidecar-drop-ext
+
+--sidecar-drop-ext

Drop the photo’s extension when naming sidecar files. By default, sidecar files are named in format ‘photo_filename.photo_ext.sidecar_ext’, e.g. ‘IMG_1234.JPG.xmp’. Use ‘–sidecar-drop-ext’ to ignore the photo extension. Resulting sidecar files will have name in format ‘IMG_1234.xmp’. Warning: this may result in sidecar filename collisions if there are files of different types but the same name in the output directory, e.g. ‘IMG_1234.JPG’ and ‘IMG_1234.MOV’.

-
---exiftool
+
+--exiftool

Use exiftool to write metadata directly to exported photos. To use this option, exiftool must be installed and in the path. exiftool may be installed from https://exiftool.org/. Cannot be used with –export-as-hardlink. Writes the following metadata: EXIF:ImageDescription, XMP:Description (see also –description-template); XMP:Title; XMP:TagsList, IPTC:Keywords, XMP:Subject (see also –keyword-template, –person-keyword, –album-keyword); XMP:PersonInImage; EXIF:GPSLatitudeRef; EXIF:GPSLongitudeRef; EXIF:GPSLatitude; EXIF:GPSLongitude; EXIF:GPSPosition; EXIF:DateTimeOriginal; EXIF:OffsetTimeOriginal; EXIF:ModifyDate (see –ignore-date-modified); IPTC:DateCreated; IPTC:TimeCreated; (video files only): QuickTime:CreationDate; QuickTime:CreateDate; QuickTime:ModifyDate (see also –ignore-date-modified); QuickTime:GPSCoordinates; UserData:GPSCoordinates.

-
---exiftool-path <EXIFTOOL_PATH>
+
+--exiftool-path <EXIFTOOL_PATH>

Optionally specify path to exiftool; if not provided, will look for exiftool in $PATH.

-
---exiftool-option <OPTION>
+
+--exiftool-option <OPTION>

Optional flag/option to pass to exiftool when using –exiftool. For example, –exiftool-option ‘-m’ to ignore minor warnings. Specify these as you would on the exiftool command line. See exiftool docs at https://exiftool.org/exiftool_pod.html for full list of options. More than one option may be specified by repeating the option, e.g. –exiftool-option ‘-m’ –exiftool-option ‘-F’.

-
---exiftool-merge-keywords
+
+--exiftool-merge-keywords

Merge any keywords found in the original file with keywords used for ‘–exiftool’ and ‘–sidecar’.

-
---exiftool-merge-persons
+
+--exiftool-merge-persons

Merge any persons found in the original file with persons used for ‘–exiftool’ and ‘–sidecar’.

-
---ignore-date-modified
+
+--ignore-date-modified

If used with –exiftool or –sidecar, will ignore the photo modification date and set EXIF:ModifyDate to EXIF:DateTimeOriginal; this is consistent with how Photos handles the EXIF:ModifyDate tag.

-
---person-keyword
+
+--person-keyword

Use person in image as keyword/tag when exporting metadata.

-
---album-keyword
+
+--album-keyword

Use album name as keyword/tag when exporting metadata.

-
---keyword-template <TEMPLATE>
+
+--keyword-template <TEMPLATE>

For use with –exiftool, –sidecar; specify a template string to use as keyword in the form ‘{name,DEFAULT}’ This is the same format as –directory. For example, if you wanted to add the full path to the folder and album photo is contained in as a keyword when exporting you could specify –keyword-template “{folder_album}” You may specify more than one template, for example –keyword-template “{folder_album}” –keyword-template “{created.year}”. See ‘–replace-keywords’ and Templating System below.

-
---replace-keywords
+
+--replace-keywords

Replace keywords with any values specified with –keyword-template. By default, –keyword-template will add keywords to any keywords already associated with the photo. If –replace-keywords is specified, values from –keyword-template will replace any existing keywords instead of adding additional keywords.

-
---description-template <TEMPLATE>
+
+--description-template <TEMPLATE>

For use with –exiftool, –sidecar; specify a template string to use as description in the form ‘{name,DEFAULT}’ This is the same format as –directory. For example, if you wanted to append ‘exported with osxphotos on [today’s date]’ to the description, you could specify –description-template “{descr} exported with osxphotos on {today.date}” See Templating System below.

-
---finder-tag-template <TEMPLATE>
+
+--finder-tag-template <TEMPLATE>

Set MacOS Finder tags to TEMPLATE. These tags can be searched in the Finder or Spotlight with ‘tag:tagname’ format. For example, ‘–finder-tag-template “{label}”’ to set Finder tags to photo labels. You may specify multiple TEMPLATE values by using ‘–finder-tag-template’ multiple times. See also ‘–finder-tag-keywords and Extended Attributes below.’.

-
---finder-tag-keywords
+
+--finder-tag-keywords

Set MacOS Finder tags to keywords; any keywords specified via ‘–keyword-template’, ‘–person-keyword’, etc. will also be used as Finder tags. See also ‘–finder-tag-template and Extended Attributes below.’.

-
---xattr-template <ATTRIBUTE TEMPLATE>
-

Set extended attribute ATTRIBUTE to TEMPLATE value. Valid attributes are: ‘authors’, ‘comment’, ‘copyright’, ‘description’, ‘findercomment’, ‘headline’, ‘keywords’. For example, to set Finder comment to the photo’s title and description: ‘–xattr-template findercomment “{title}; {descr}” See Extended Attributes below for additional details on this option.

+
+--xattr-template <ATTRIBUTE TEMPLATE>
+

Set extended attribute ATTRIBUTE to TEMPLATE value. Valid attributes are: ‘authors’, ‘comment’, ‘copyright’, ‘creator’, ‘description’, ‘findercomment’, ‘headline’, ‘keywords’, ‘participants’, ‘projects’, ‘rating’, ‘subject’, ‘title’, ‘version’. For example, to set Finder comment to the photo’s title and description: ‘–xattr-template findercomment “{title}; {descr}” See Extended Attributes below for additional details on this option.

-
---directory <DIRECTORY>
+
+--directory <DIRECTORY>

Optional template for specifying name of output directory in the form ‘{name,DEFAULT}’. See below for additional details on templating system.

-
---filename <FILENAME>
+
+--filename <FILENAME>

Optional template for specifying name of output file in the form ‘{name,DEFAULT}’. File extension will be added automatically–do not include an extension in the FILENAME template. See below for additional details on templating system.

-
---jpeg-ext <EXTENSION>
+
+--jpeg-ext <EXTENSION>

Specify file extension for JPEG files. Photos uses .jpeg for edited images but many images are imported with .jpg or .JPG which can result in multiple different extensions used for JPEG files upon export. Use –jpeg-ext to specify a single extension to use for all exported JPEG images. Valid values are jpeg, jpg, JPEG, JPG; e.g. ‘–jpeg-ext jpg’ to use ‘.jpg’ for all JPEGs.

Options
@@ -772,93 +820,105 @@ to modify this behavior.

-
---strip
+
+--strip

Optionally strip leading and trailing whitespace from any rendered templates. For example, if –filename template is “{title,} {original_name}” and image has no title, resulting file would have a leading space but if used with –strip, this will be removed.

-
---edited-suffix <SUFFIX>
+
+--edited-suffix <SUFFIX>

Optional suffix template for naming edited photos. Default name for edited photos is in form ‘photoname_edited.ext’. For example, with ‘–edited-suffix _bearbeiten’, the edited photo would be named ‘photoname_bearbeiten.ext’. The default suffix is ‘_edited’. Multi-value templates (see Templating System) are not permitted with –edited-suffix.

-
---original-suffix <SUFFIX>
+
+--original-suffix <SUFFIX>

Optional suffix template for naming original photos. Default name for original photos is in form ‘filename.ext’. For example, with ‘–original-suffix _original’, the original photo would be named ‘filename_original.ext’. The default suffix is ‘’ (no suffix). Multi-value templates (see Templating System) are not permitted with –original-suffix.

-
---use-photos-export
+
+--use-photos-export

Force the use of AppleScript or PhotoKit to export even if not missing (see also ‘–download-missing’ and ‘–use-photokit’).

-
---use-photokit
+
+--use-photokit

Use with ‘–download-missing’ or ‘–use-photos-export’ to use direct Photos interface instead of AppleScript to export. Highly experimental alpha feature; does not work with iTerm2 (use with Terminal.app). This is faster and more reliable than the default AppleScript interface.

-
---report <path to export report>
+
+--report <path to export report>

Write a CSV formatted report of all files that were exported.

-
---cleanup
+
+--cleanup

Cleanup export directory by deleting any files which were not included in this export set. For example, photos which had previously been exported and were subsequently deleted in Photos. WARNING: –cleanup will delete any files in the export directory that were not exported by osxphotos, for example, your own scripts or other files. Be sure this is what you intend before using –cleanup. Use –dry-run with –cleanup first if you’re not certain.

-
---add-exported-to-album <ALBUM>
+
+--add-exported-to-album <ALBUM>

Add all exported photos to album ALBUM in Photos. Album ALBUM will be created if it doesn’t exist. All exported photos will be added to this album. This only works if the Photos library being exported is the last-opened (default) library in Photos. This feature is currently experimental. I don’t know how well it will work on large export sets.

-
---add-skipped-to-album <ALBUM>
+
+--add-skipped-to-album <ALBUM>

Add all skipped photos to album ALBUM in Photos. Album ALBUM will be created if it doesn’t exist. All skipped photos will be added to this album. This only works if the Photos library being exported is the last-opened (default) library in Photos. This feature is currently experimental. I don’t know how well it will work on large export sets.

-
---add-missing-to-album <ALBUM>
+
+--add-missing-to-album <ALBUM>

Add all missing photos to album ALBUM in Photos. Album ALBUM will be created if it doesn’t exist. All missing photos will be added to this album. This only works if the Photos library being exported is the last-opened (default) library in Photos. This feature is currently experimental. I don’t know how well it will work on large export sets.

-
---exportdb <EXPORTDB_FILE>
+
+--post-command <CATEGORY COMMAND>
+

Run COMMAND on exported files of category CATEGORY. CATEGORY can be one of: exported, new, updated, skipped, missing, exif_updated, touched, converted_to_jpeg, sidecar_json_written, sidecar_json_skipped, sidecar_exiftool_written, sidecar_exiftool_skipped, sidecar_xmp_written, sidecar_xmp_skipped, error. COMMAND is an osxphotos template string, for example: ‘–post-command exported “echo {filepath|shell_quote} >> {export_dir}/exported.txt”’, which appends the full path of all exported files to the file ‘exported.txt’. You can run more than one command by repeating the ‘–post-command’ option with different arguments. See Post Command below.

+
+ +
+
+--post-function <filename.py::function>
+

Run function on exported files. Use this in format: –post-function filename.py::function where filename.py is a python file you’ve created and function is the name of the function in the python file you want to call. The function will be passed information about the photo that’s been exported and a list of all exported files associated with the photo. You can run more than one function by repeating the ‘–post-function’ option with different arguments. See Post Function below.

+
+ +
+
+--exportdb <EXPORTDB_FILE>

Specify alternate name for database file which stores state information for export and –update. If –exportdb is not specified, export database will be saved to ‘.osxphotos_export.db’ in the export directory. Must be specified as filename only, not a path, as export database will be saved in export directory.

-
---load-config <config file path>
+
+--load-config <config file path>

Load options from file as written with –save-config. This allows you to save a complex export command to file for later reuse. For example: ‘osxphotos export <lots of options here> –save-config osxphotos.toml’ then ‘osxphotos export /path/to/export –load-config osxphotos.toml’. If any other command line options are used in conjunction with –load-config, they will override the corresponding values in the config file.

-
---save-config <config file path>
+
+--save-config <config file path>

Save options to file for use with –load-config. File format is TOML.

Arguments

-
-PHOTOS_LIBRARY
+
+PHOTOS_LIBRARY

Optional argument(s)

-
-DEST
+
+DEST

Required argument

@@ -871,8 +931,8 @@ to modify this behavior.

Arguments

-
-TOPIC
+
+TOPIC

Optional argument

@@ -885,21 +945,21 @@ to modify this behavior.

Options

-
---db <Photos database path>
+
+--db <Photos database path>

Specify Photos database path. Path to Photos library/database can be specified using either –db or directly as PHOTOS_LIBRARY positional argument. If neither –db or PHOTOS_LIBRARY provided, will attempt to find the library to use in the following order: 1. last opened library, 2. system library, 3. ~/Pictures/Photos Library.photoslibrary

-
---json
+
+--json

Print output in JSON format.

Arguments

-
-PHOTOS_LIBRARY
+
+PHOTOS_LIBRARY

Optional argument(s)

@@ -912,21 +972,21 @@ to modify this behavior.

Options

-
---db <Photos database path>
+
+--db <Photos database path>

Specify Photos database path. Path to Photos library/database can be specified using either –db or directly as PHOTOS_LIBRARY positional argument. If neither –db or PHOTOS_LIBRARY provided, will attempt to find the library to use in the following order: 1. last opened library, 2. system library, 3. ~/Pictures/Photos Library.photoslibrary

-
---json
+
+--json

Print output in JSON format.

Arguments

-
-PHOTOS_LIBRARY
+
+PHOTOS_LIBRARY

Optional argument(s)

@@ -939,21 +999,21 @@ to modify this behavior.

Options

-
---db <Photos database path>
+
+--db <Photos database path>

Specify Photos database path. Path to Photos library/database can be specified using either –db or directly as PHOTOS_LIBRARY positional argument. If neither –db or PHOTOS_LIBRARY provided, will attempt to find the library to use in the following order: 1. last opened library, 2. system library, 3. ~/Pictures/Photos Library.photoslibrary

-
---json
+
+--json

Print output in JSON format.

Arguments

-
-PHOTOS_LIBRARY
+
+PHOTOS_LIBRARY

Optional argument(s)

@@ -966,8 +1026,8 @@ to modify this behavior.

Options

-
---json
+
+--json

Print output in JSON format.

@@ -980,21 +1040,21 @@ to modify this behavior.

Options

-
---db <Photos database path>
+
+--db <Photos database path>

Specify Photos database path. Path to Photos library/database can be specified using either –db or directly as PHOTOS_LIBRARY positional argument. If neither –db or PHOTOS_LIBRARY provided, will attempt to find the library to use in the following order: 1. last opened library, 2. system library, 3. ~/Pictures/Photos Library.photoslibrary

-
---json
+
+--json

Print output in JSON format.

Arguments

-
-PHOTOS_LIBRARY
+
+PHOTOS_LIBRARY

Optional argument(s)

@@ -1007,21 +1067,21 @@ to modify this behavior.

Options

-
---db <Photos database path>
+
+--db <Photos database path>

Specify Photos database path. Path to Photos library/database can be specified using either –db or directly as PHOTOS_LIBRARY positional argument. If neither –db or PHOTOS_LIBRARY provided, will attempt to find the library to use in the following order: 1. last opened library, 2. system library, 3. ~/Pictures/Photos Library.photoslibrary

-
---json
+
+--json

Print output in JSON format.

Arguments

-
-PHOTOS_LIBRARY
+
+PHOTOS_LIBRARY

Optional argument(s)

@@ -1036,435 +1096,493 @@ if more than one option is provided, they are treated as “AND”

Options

-
---db <Photos database path>
+
+--db <Photos database path>

Specify Photos database path. Path to Photos library/database can be specified using either –db or directly as PHOTOS_LIBRARY positional argument. If neither –db or PHOTOS_LIBRARY provided, will attempt to find the library to use in the following order: 1. last opened library, 2. system library, 3. ~/Pictures/Photos Library.photoslibrary

-
---json
+
+--json

Print output in JSON format.

-
---keyword <KEYWORD>
+
+--keyword <KEYWORD>

Search for photos with keyword KEYWORD. If more than one keyword, treated as “OR”, e.g. find photos matching any keyword

-
---person <PERSON>
+
+--person <PERSON>

Search for photos with person PERSON. If more than one person, treated as “OR”, e.g. find photos matching any person

-
---album <ALBUM>
+
+--album <ALBUM>

Search for photos in album ALBUM. If more than one album, treated as “OR”, e.g. find photos matching any album

-
---folder <FOLDER>
+
+--folder <FOLDER>

Search for photos in an album in folder FOLDER. If more than one folder, treated as “OR”, e.g. find photos in any FOLDER. Only searches top level folders (e.g. does not look at subfolders)

-
---name <FILENAME>
+
+--name <FILENAME>

Search for photos with filename matching FILENAME. If more than one –name options is specified, they are treated as “OR”, e.g. find photos matching any FILENAME.

-
---uuid <UUID>
+
+--uuid <UUID>

Search for photos with UUID(s).

-
---uuid-from-file <FILE>
+
+--uuid-from-file <FILE>

Search for photos with UUID(s) loaded from FILE. Format is a single UUID per line. Lines preceded with # are ignored.

-
---title <TITLE>
+
+--title <TITLE>

Search for TITLE in title of photo.

-
---no-title
+
+--no-title

Search for photos with no title.

-
---description <DESC>
+
+--description <DESC>

Search for DESC in description of photo.

-
---no-description
+
+--no-description

Search for photos with no description.

-
---place <PLACE>
+
+--place <PLACE>

Search for PLACE in photo’s reverse geolocation info

-
---no-place
+
+--no-place

Search for photos with no associated place name info (no reverse geolocation info)

-
---label <LABEL>
+
+--location
+

Search for photos with associated location info (e.g. GPS coordinates)

+
+ +
+
+--no-location
+

Search for photos with no associated location info (e.g. no GPS coordinates)

+
+ +
+
+--label <LABEL>

Search for photos with image classification label LABEL (Photos 5 only). If more than one label, treated as “OR”, e.g. find photos matching any label

-
---uti <UTI>
+
+--uti <UTI>

Search for photos whose uniform type identifier (UTI) matches UTI

-
--i, --ignore-case
+
+-i, --ignore-case

Case insensitive search for title, description, place, keyword, person, or album.

-
---edited
+
+--edited

Search for photos that have been edited.

-
---external-edit
+
+--external-edit

Search for photos edited in external editor.

-
---favorite
+
+--favorite

Search for photos marked favorite.

-
---not-favorite
+
+--not-favorite

Search for photos not marked favorite.

-
---hidden
+
+--hidden

Search for photos marked hidden.

-
---not-hidden
+
+--not-hidden

Search for photos not marked hidden.

-
---shared
+
+--shared

Search for photos in shared iCloud album (Photos 5 only).

-
---not-shared
+
+--not-shared

Search for photos not in shared iCloud album (Photos 5 only).

-
---burst
+
+--burst

Search for photos that were taken in a burst.

-
---not-burst
+
+--not-burst

Search for photos that are not part of a burst.

-
---live
+
+--live

Search for Apple live photos

-
---not-live
+
+--not-live

Search for photos that are not Apple live photos.

-
---portrait
+
+--portrait

Search for Apple portrait mode photos.

-
---not-portrait
+
+--not-portrait

Search for photos that are not Apple portrait mode photos.

-
---screenshot
+
+--screenshot

Search for screenshot photos.

-
---not-screenshot
+
+--not-screenshot

Search for photos that are not screenshot photos.

-
---slow-mo
+
+--slow-mo

Search for slow motion videos.

-
---not-slow-mo
+
+--not-slow-mo

Search for photos that are not slow motion videos.

-
---time-lapse
+
+--time-lapse

Search for time lapse videos.

-
---not-time-lapse
+
+--not-time-lapse

Search for photos that are not time lapse videos.

-
---hdr
+
+--hdr

Search for high dynamic range (HDR) photos.

-
---not-hdr
+
+--not-hdr

Search for photos that are not HDR photos.

-
---selfie
+
+--selfie

Search for selfies (photos taken with front-facing cameras).

-
---not-selfie
+
+--not-selfie

Search for photos that are not selfies.

-
---panorama
+
+--panorama

Search for panorama photos.

-
---not-panorama
+
+--not-panorama

Search for photos that are not panoramas.

-
---has-raw
+
+--has-raw

Search for photos with both a jpeg and raw version

-
---only-movies
+
+--only-movies

Search only for movies (default searches both images and movies).

-
---only-photos
+
+--only-photos

Search only for photos/images (default searches both images and movies).

-
---from-date <from_date>
+
+--from-date <from_date>

Search by item start date, e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601 with/without timezone).

-
---to-date <to_date>
+
+--to-date <to_date>

Search by item end date, e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601 with/without timezone).

-
---from-time <from_time>
+
+--from-time <from_time>

Search by item start time of day, e.g. 12:00, or 12:00:00.

-
---to-time <to_time>
+
+--to-time <to_time>

Search by item end time of day, e.g. 12:00 or 12:00:00.

-
---has-comment
+
+--has-comment

Search for photos that have comments.

-
---no-comment
+
+--no-comment

Search for photos with no comments.

-
---has-likes
+
+--has-likes

Search for photos that have likes.

-
---no-likes
+
+--no-likes

Search for photos with no likes.

-
---is-reference
+
+--is-reference

Search for photos that were imported as referenced files (not copied into Photos library).

-
---in-album
+
+--in-album

Search for photos that are in one or more albums.

-
---not-in-album
+
+--not-in-album

Search for photos that are not in any albums.

-
---min-size <SIZE>
+
+--duplicate
+

Search for photos with possible duplicates. osxphotos will compare signatures of photos, evaluating date created, size, height, width, and edited status to find possible duplicates. This does not compare images byte-for-byte nor compare hashes but should find photos imported multiple times or duplicated within Photos.

+
+ +
+
+--min-size <SIZE>

Search for photos with size >= SIZE bytes. The size evaluated is the photo’s original size (when imported to Photos). Size may be specified as integer bytes or using SI or NIST units. For example, the following are all valid and equivalent sizes: ‘1048576’ ‘1.048576MB’, ‘1 MiB’.

-
---max-size <SIZE>
+
+--max-size <SIZE>

Search for photos with size <= SIZE bytes. The size evaluated is the photo’s original size (when imported to Photos). Size may be specified as integer bytes or using SI or NIST units. For example, the following are all valid and equivalent sizes: ‘1048576’ ‘1.048576MB’, ‘1 MiB’.

-
---regex <REGEX TEMPLATE>
+
+--regex <REGEX TEMPLATE>

Search for photos where TEMPLATE matches regular expression REGEX. For example, to find photos in an album that begins with ‘Beach’: ‘–regex “^Beach” “{album}”’. You may specify more than one regular expression match by repeating ‘–regex’ with different arguments.

-
---query-eval <CRITERIA>
+
+--selected
+

Filter for photos that are currently selected in Photos.

+
+ +
+
+--query-eval <CRITERIA>

Evaluate CRITERIA to filter photos. CRITERIA will be evaluated in context of the following python list comprehension: photos = [photo for photo in photos if CRITERIA] where photo represents a PhotoInfo object. For example: –query-eval photo.favorite returns all photos that have been favorited and is equivalent to –favorite. You may specify more than one CRITERIA by using –query-eval multiple times. CRITERIA must be a valid python expression. See https://rhettbull.github.io/osxphotos/ for additional documentation on the PhotoInfo class.

-
---deleted
+
+--query-function <filename.py::function>
+

Run function to filter photos. Use this in format: –query-function filename.py::function where filename.py is a python file you’ve created and function is the name of the function in the python file you want to call. Your function will be passed a list of PhotoInfo objects and is expected to return a filtered list of PhotoInfo objects. You may use more than one function by repeating the –query-function option with a different value. Your query function will be called after all other query options have been evaluated. See https://github.com/RhetTbull/osxphotos/blob/master/examples/query_function.py for example of how to use this option.

+
+ +
+
+--deleted

Include photos from the ‘Recently Deleted’ folder.

-
---deleted-only
+
+--deleted-only

Include only photos from the ‘Recently Deleted’ folder.

-
---missing
+
+--missing

Search for photos missing from disk.

-
---not-missing
+
+--not-missing

Search for photos present on disk (e.g. not missing).

-
---cloudasset
+
+--cloudasset

Search for photos that are part of an iCloud library

-
---not-cloudasset
+
+--not-cloudasset

Search for photos that are not part of an iCloud library

-
---incloud
+
+--incloud

Search for photos that are in iCloud (have been synched)

-
---not-incloud
+
+--not-incloud

Search for photos that are not in iCloud (have not been synched)

-
---add-to-album <ALBUM>
+
+--add-to-album <ALBUM>

Add all photos from query to album ALBUM in Photos. Album ALBUM will be created if it doesn’t exist. All photos in the query results will be added to this album. This only works if the Photos library being queried is the last-opened (default) library in Photos. This feature is currently experimental. I don’t know how well it will work on large query sets.

Arguments

-
-PHOTOS_LIBRARY
+
+PHOTOS_LIBRARY
+

Optional argument(s)

+
+ + +
+

repl

+

Run interactive osxphotos shell

+
osxphotos repl [OPTIONS]
+
+
+

Options

+
+
+--db <Photos database path>
+

Specify Photos database path. Path to Photos library/database can be specified using either –db or directly as PHOTOS_LIBRARY positional argument. If neither –db or PHOTOS_LIBRARY provided, will attempt to find the library to use in the following order: 1. last opened library, 2. system library, 3. ~/Pictures/Photos Library.photoslibrary

+
+ +
+
+

tutorial

+

Display osxphotos tutorial.

+
osxphotos tutorial [OPTIONS] [WIDTH]...
+
+
+

Arguments

+
+
+WIDTH

Optional argument(s)

@@ -1504,6 +1622,8 @@ if more than one option is provided, they are treated as “AND”
  • persons
  • places
  • query
  • +
  • repl
  • +
  • tutorial
  • @@ -1546,7 +1666,7 @@ if more than one option is provided, they are treated as “AND” ©2021, Rhet Turnbull. | - Powered by Sphinx 3.5.2 + Powered by Sphinx 4.0.2 & Alabaster 0.7.12 | diff --git a/docs/genindex.html b/docs/genindex.html index 13ec163c..d70dcae5 100644 --- a/docs/genindex.html +++ b/docs/genindex.html @@ -5,10 +5,10 @@ - Index — osxphotos 0.42.20 documentation - - - + Index — osxphotos 0.42.66 documentation + + + @@ -168,6 +168,8 @@
  • osxphotos-places command line option
  • osxphotos-query command line option +
  • +
  • osxphotos-repl command line option
  • @@ -227,6 +229,15 @@
  • +
  • + --duplicate + +
  • @@ -538,6 +549,15 @@
  • +
  • + --location + +
  • @@ -585,8 +605,6 @@
  • osxphotos-query command line option
  • - - + + +
  • + --post-command <CATEGORY COMMAND> + +
  • +
  • + --post-function <filename.py::function> + +
  • +
  • + --preview + +
  • +
  • + --preview-if-missing + +
  • +
  • + --preview-suffix <SUFFIX> + +
  • @@ -850,6 +914,15 @@
  • osxphotos-export command line option
  • osxphotos-query command line option +
  • + +
  • + --query-function <filename.py::function> + +
  • @@ -896,6 +969,15 @@
  • osxphotos-export command line option
  • osxphotos-query command line option +
  • + +
  • + --selected + +
  • @@ -1122,19 +1204,19 @@

    A

    @@ -1198,15 +1280,15 @@
  • camera_model (osxphotos.PhotoInfo.ExifInfo attribute)
  • -
  • city() (osxphotos.PhotoInfo.SearchInfo property) +
  • city (osxphotos.PhotoInfo.SearchInfo property)
    • codec (osxphotos.PhotoInfo.ExifInfo attribute)
    • -
    • comments() (osxphotos.PhotoInfo property) +
    • comments (osxphotos.PhotoInfo property)
    • -
    • country() (osxphotos.PhotoInfo.SearchInfo property) +
    • country (osxphotos.PhotoInfo.SearchInfo property)
    • curation (osxphotos.PhotoInfo.ScoreInfo attribute)
    • @@ -1216,21 +1298,21 @@

      D

      @@ -1247,9 +1331,9 @@

      E

      +
    • duplicates (osxphotos.PhotoInfo property) +
    • duration (osxphotos.PhotoInfo.ExifInfo attribute)
    • @@ -1267,13 +1351,13 @@

      F

      @@ -1331,37 +1415,37 @@ @@ -1377,7 +1461,7 @@

      K

      @@ -1393,7 +1477,7 @@

      L

      • lens_model (osxphotos.PhotoInfo.ExifInfo attribute)
      • -
      • library_path() (osxphotos.PhotosDB property) +
      • library_path (osxphotos.PhotosDB property)
      • -
      • likes() (osxphotos.PhotoInfo property) +
      • likes (osxphotos.PhotoInfo property)
      • -
      • live_photo() (osxphotos.PhotoInfo property) +
      • live_photo (osxphotos.PhotoInfo property)
      • lively_color (osxphotos.PhotoInfo.ScoreInfo attribute)
      • -
      • locality_names() (osxphotos.PhotoInfo.SearchInfo property) +
      • locality_names (osxphotos.PhotoInfo.SearchInfo property)
      • -
      • location() (osxphotos.PhotoInfo property) +
      • location (osxphotos.PhotoInfo property)
      • longitude (osxphotos.PhotoInfo.ExifInfo attribute)
      • @@ -1439,13 +1523,13 @@

        M

        @@ -1453,7 +1537,7 @@

        N

        @@ -369,7 +373,7 @@ Alternatively, you can also run the command line utility like this: Sphinx 3.5.2 + Powered by Sphinx 4.0.2 & Alabaster 0.7.12 | diff --git a/docs/modules.html b/docs/modules.html index e884381f..cc0d618c 100644 --- a/docs/modules.html +++ b/docs/modules.html @@ -5,10 +5,10 @@ - osxphotos — osxphotos 0.42.20 documentation - - - + osxphotos — osxphotos 0.42.66 documentation + + + @@ -91,7 +91,7 @@ ©2021, Rhet Turnbull. | - Powered by Sphinx 3.5.2 + Powered by Sphinx 4.0.2 & Alabaster 0.7.12 | diff --git a/docs/objects.inv b/docs/objects.inv index f58644bc0aadbf8b8f6200db648e4bcd703a74b8..c9829e3b86ebbed378c9ea03072788c4e2b0f871 100644 GIT binary patch delta 3639 zcmV-74#@F@8;l*0cz>MDj^wr#h4=Fm9XN|*p$3wrckvJ40c?0+4{T&5uq0L$r&XdX zQq^5vPyJ<4VTxUvitiEt)Vb5< z%5v3kdXLmCD}P?{qiiosQ(3QPq0NwE6*C$b*2q@>!c|vk1%>TE|IDh&4BU$MBy&H{ zvV1@4*Fv@&oISku`M}TYDrC#hjy<6YT6CO1MWi)?Q>SWT)~%Rmy=g>S1bA$H9?*!R_lDot+c50j!=0qiH+E$ z^Lc208%!76kJvCfLG5^nCIvUMof&jjz9UpBJ33@x!osd!-zY0y?0b{c%|tQX(xajI z!WX&AwSOrS{u>hzOj6q^UhWpbG*g-%yUL&sW}3p&?g{zIO(Ia7r3r+)U1rZbwq}@@ z`!coYk>p%mM0AZ#cXkx>jk-w3p`8uN_bng;4S% z>whYfl#1eo!g=90iroF0LQvc$z%Fsy&M>PSLLV;Sh*kDcsw~m#?)4XwU>pSrpID%q8& zxQ{QOSoO|kMaeZvxCp=B0?Fl7a8%s?3Khe>+(^d{Vt_A0F26N0nMB}Kw&>D?vI=zC zytW5RmG~2#%_{MN&O>&)Iw#z3yLiQCQ4@|$M56&t!cHo*=zND!=0*@Pl$hxu`+qgg zt0>WBe8WuVWEY02abJBlnSLv-%$PV=*S8HId!P?tX&{*h_xpKvL?)_4uIYb zyK@6dO)AhbG;cKRm5+k~B=FjF#$5A@`4TRJ+&arByMK+jo3#q(^|;p!==*IG!fg)# zYbUwfE(0UN-cHh<(5YZOQIx*8@o6Gf1(e;|;qr99+Nwliq2 zw)h4}^sM|SD?2&CUBT(GyLjR{H2?WwUk`~tB2Qqp?{dvalrNC zB;CQCGO0%?v}+QFmbEv0JWUOf5%5RH-3j~qbp2np znV)n2V{Z4RMf|yfY{6T79^oBUcJ!ypVc+W(iXU3`X}m~OzP`_K5LL*KSdrRd6&l$tjfdm!+$c%c}TUN&OQ{Ccv8Cjx|D^Ln1A*ke)1oFvd3VoE}tq< z^QXR4)SePN{q5TyKmPs0iPyp`+}?UdAEYd0{H;TMv_F2BJ~mhdpZJ|u#)Utb3CjEa z??3-lPv)cDW!$6r%LjX?wRCH4+4vt0_meC;lV3XDOaBiQIuE~^eSguZeKVJ|JZD*X z=yk=Vl#+}R*$}g{jFsi^ z5A)Tuba#B4pOYs@Nq^JfXoc>%03|Kei)flXzUBfHx5PajK(is|#o?Y1HE~rc2;3>7 z(4yk3?ivU?YU5y8n}wUUcjHULPnf}s5jV0GzO~N!BM!(V1Vk;i#?S#gi=q<_iK8Pk z5=rM^AeP?Km(et^n5siChe`j)AoF|5M5hT9w-l{epvx9)Gk>p+!{uvTN#-LW<}ruF zt=e1GcHn&(v~loQ(SxDAItm|lQW5eXUBPCmJ08Uwfj$!ACS$D;#2>}0={AdXMqA;NQV_M6KIOJh;pm>qh+^3dyD?Buw0_uCg<0OS1rb0i70@HM_$tE74Ip zL#)_m*he2f89mi0PzJtT@%O2)jvoP03(C&L>Yl~$pR{>o0f}0_9t80r%SL)|GxAH-4gg}r5c4Jd;4;yBt2VH`b*6KH(p zy}ICr6Mw#RvR6ye#2?60cFK%T^tGM76*(gL1`yf}&WW$Y;41JV{_i85?40+JR% z9WZJVAksFjLKn7h@-*I+>%n*@Pva~3Y7%~&KY!?x26jc8xmsYuJz7BmKS(iJRe z88t)1ZQ#6QB`F~V=k>sBUyzlJRY({<0HiIBU0uKl;XNU4VqZG~Mubtj2F}Ehv`C3y zR)6gX(P4&&+Vqu@8NV`YIUdMY@T7Y_Lqu&_e&SdgIb{Ock3`x={sNM+vTrf~4XiYQ z)!;$7Q%0eRT{&e>B-FjDzDdJ&CV4P(PI)Ta#yWe=e~ZGQkS87Flb?vq3_6(LBiZ@+iJ*;Px1# zajfEQh4=zy^ml-Di}80fA5V#!X@TK&;t;ie`UfIfVG`+1^x@tzLqu)NqWK`fF@OJC z9QkHn4of?nn;~L0zwLO*V!s3R@4o-_GghnJp0ZvXfl?k32=FTp37mP=Jb(WA)5mYO z(XFm$J8g*UpKsb;=4@s2BIg0M{jrN4ws`EqJ$XfF(;oYvMaOj%&;+A&-V6b9dsBGm zqB%U4(j*@Lpjq6Lt3abT;k-#aZhw1&48TQm3`k024CqHqA(Y0h+z6M~Jru$MO*<&( zZ94(CU*d>NUL~b@NBD#G?e{aF_UMI+<`{86YdGMtF&vWA77qQODf+Fc;n67Uktnyh zb*GOSz~m$nIDmG@<=OLy4Bi$9a07J=zO}bG=&ZAO81hulK#V-pGs6PU^ncv3myh&J z24FqN$dA{8cs%y($N-N%Ix?b>jfY`#dAZHtp?D$sz)seoj}}-d#js}`tD^8=ppJUL zF3YGr6sw{hxX~i&JM{L|dw^an-$yiDLG9qF0_qSCR5(rWWrb6S-n`2-f#-W%9$P{k zV_sL|aX?AUKwMVSj8M34+JBKn99rX3U9>P~0=t*!LR=?}V&P)tft{*V9u2l9MGm~k ziAErF?CqGbKNF&8Da9w;djL!!VofD`knmGaua1fF}M#ecOSn9!k9rL_Sb zs9^EzWerOJ1`aJeczJ9wfi=FUi5Jy|XxidtMo5`Zvdv`OZfF2z7O|?`J1m@GBx-g; zI3+1aR_xvzz{x=pt$_K!52+0AE&r+`*BP^ne~YOb_ws(f8z#IQIQVIq%Hu1!F46D;z4yKpb~=VS|Ftb(SHX46i&p`SvXWXG(cr~ z_YtZb^4R3g+@kVWbxlSLxHWqk|=V1oezUw#N?{g(cYV!P-c7k1(w-0Ow!?cDF2!o4~8>688J J{{bO@ZiTSB3wi(m delta 3373 zcmV+|4bt+A9fTW@cz>PCuH?26hWGOn9XN|*p#wNe@8S!P0UXT0HXLLnuqAdk=SZR~ zQhiQ)mDkAY|`d+B|nLdmt>SISd~}2 zIqM6V)spW_C`mz{3EIC#&qk&=-(+XH-G+jx*@QM1@DRnR8)XEgKc3lvCo$8?F?c)|j=&id~+AV{xUelYdeD@e42X9?Y({cbiKG%v)oD z;|p>oR|Z<$c6!p4e9PUFCX*T|6#eGp)jZXTzDA9cXNomCA95=#D!n6A)+VtLyG*_b z>K=ocg8LC$W&_lYmuOOO6Yb2RyYd~OQrXoZ3*#1cy?vvsc(Lz8Qa9tpbVv7w=7wvz z%e5)v{(oEJ5KL0rD_-vA!8B8vpZdz6k0wmvY4?nL<0cU(F48!{-7eEHPn}uj<-SZU z9Mb$4<#E;(7c_I3#4k3=U5&E=)29IB9V>;PO5w+s06EIi+)FM``I||@J0h(ZauW(d z_RX-S36zVhM9FT{hg7j6ugDO)!8HSuH)dT>VSi>B2ophXEuADAzu_h@8gAm`QFa$8 zOX~Hs6)X5IW28)c@V=Y^(ZjZs%E;OA*zv2FwkafF$GN7CkIDLbzRMhPBr4=){ue>8 z@=LFD%^Pe$Sas~{TnrHbQe$>uw|(q8g$r}q4F?Q`>q~xOePvQiQM}@mSAN6A{$pIP zxPMJ#eFF9@!R%Gw`C5YhsB}K4EU_zg>e?g;CqV${j;RZ%=c?#Li#kvKE>7^Aw_+`2 zg=uZk4OjjRliEEdt8$YiK(cTE_QF)&u{Tgk3?r8k3SJjJ z7J8mm3bU4>wIFIcvEaumnxc|@iAkLYfPbXQSvC7Lt}%TrrdkFdmRG?ssrv|%g0tBO z$6rL`U%;%{gyT2aVH*H^hvk)dV~=Sn@jDh>D)EZtueR`;!%x_53DJmVT&(d`6!}@$ z5JB;{cMxR;p9H~u;e^<0P~Jp|ZHilFCL*gDrbTV)i^-@vab>m&tYC)C7O?f>jD7r$_G8+ zI?7`XV;QXRH?AV_P-2lM1DT zsRHfAW+B-!9-5;J%Jqn2$GRK3a`3ctRx4adxN)dBJ`3~XEeGP61^m|Y$A723MI{bO zm*14KIAmWR%wOHl4ioQSE9|T(50z;6!S#~alIGzr-~IUUuOH655heqEcMLaQEM@%E zqdwaI{V@O9U@G{HfAPv#;RoX%CcFRp&wtgy{Ayp1+t5FKuvPQ5wf2^cKk&GJ%Ca|M z+}o|)UsPy$_-JvX^AKh}Qh&T;S$TB2qAsPcm(L^g)%0Pu6k1sp!?|)00(8O%^7B7F z{qpJO{Kt>q{`B{s{tcm7cg_}=&`{aeEie#Pwj72L#Vc-eN`N;g)6h&d)eR?mc?AFb zgLBx`V{%kIbKTn5$-GIsC=knwI{FKWER&6Qrckp*~EvxqkvBO{!PXwtL*> z3KTbq+js!YhO`5RPlTuuf8>q8y)p_dD$bg|g|MSG4wj9{n6-TxUs`^~3@jLNL$(F5 z(V45?LRLaR)WowsbO0~B=!`@B=*WzC(j^%1rT6r;Hw{e8N6?ssS^vmDHWlZHmS#}g zr0C54NOs_uP3kyYzJE29WRpk4Y%C#hQ{64=dhodn+9-IaB_Z5Fx`EA9e>#bG0(~UJ zjf{N@5PuYJqO+QJXP!+PR*I!1>yH>7H%U9#9Z}n6CBFWbjBP`1&q*@l2ni!T@T%;> zy3*{CUO`JM%$B+1Eh|xvwm@u{0Rz?~PN025jazjfKO{trOn-ed2!9gC(c>X6gdW8S zv^`A9G&|G0K#cRqASp=3IB|on3W?e9(m1x0HNGHbbsBC@eQ>x_N8uN<;)fPS2p{T` z27F5fiudXy+@8^(XseBbOKg%@T^#6Jydb8RMzP}D$c|?PQ`qbfn^-$1v#C=Q;>SQ+ zCYAYobXzJuM|xWZXkIwNZ&DLM!86rNfMSs+qo@P8Cw5~t90Ow2IHwxAWz~+(fzE2B_ivic}d#drD}+k7qYL!nWd0>cMFqc#EVi;FuY1lf~3itk%^JO*hTtGG|J zUw^^G=nk-LV*HF|XQIT7X@TK&;ylMSa6?2ZOd|c^B0O6bh^QH}_c%&$&HoxlzT0=r z(as4Lh?tpQcf4e=-`U>xKm7a)*4f#eGbfHfDGkK|exs%M!mIY>mrtKRe)k;R>Kxl^ zL!|vG2hbF0Wy_Yx0rh;7wu7yjv>lEzrhg9mU_{5w4lo3xbU6$G^7)~dpsV4Su#_R0 z@DGN?f8_`m6fay3$%K1;KnCDyI0htTFb4Fap%6-AS8jys>mGXYfT10f%dwq-&tKw* zOkO2rct`ky@pVN9Xgr*7H5?-j7!40x4~9pQ#=@f?427#54PT_dzChutPj~vL0e_gB zL;~k{9=Sfk8j=`UuZ63OqY!eLm4!}T+d<9^E1-=1r$6kK{U^W1rdXN10 ziT8*{ssls)bX^OWpm^izz)tq9jwY~UV^5Es7*AlJ?{dJd+b$<4)^#~>qfM80=;ycC z0eZE^9?@{erH7{)E<-%fY%#;v&3_gl`sq^C3||g5#alJH(g_|1(cB;2=H1K@p;=!xLWduS;e4ZIQ+8ePz1E7bl z`~N27(}nASxGY(ZK&&^u>vQh_3{>bR@VZ7ngEgMP#~blNG%b@~5K=}c zSzEH#FEju~=vb%U9Ttw%i9Wv&P6@8bF28#NaF7k)y8!Npn*VTomq}He3_#mNr}x!5wY6BAOd|;D3VPR5qQ*LainP zRHo}-P@~C&P1eAkZgB9(Wq-qjd%C@0;4QlwLLl1QFryQ_4TBM}S0RaVJTn|_Y@W^_ zZ(lnu0MA!;mNgyU3!-`?Q_wVyfUkvl^^?&yjv*87R_wPXr-gVfr z;+38G2mf~KdvpFXuO{&S*4E*({axK_vwa)F)?i@#%>TCk*P;(M*>{KGU4#Dz_a=|D DP#laO diff --git a/docs/reference.html b/docs/reference.html index 6645766e..5d526a26 100644 --- a/docs/reference.html +++ b/docs/reference.html @@ -5,10 +5,10 @@ - osxphotos package — osxphotos 0.42.20 documentation - - - + osxphotos package — osxphotos 0.42.66 documentation + + + @@ -36,75 +36,75 @@

        osxphotos module

        -
        -class osxphotos.PhotosDB(dbfile=None, verbose=None, exiftool=None)[source]
        +
        +class osxphotos.PhotosDB(dbfile=None, verbose=None, exiftool=None)[source]

        Processes a Photos.app library database to extract information about photos

        -
        -
        -property album_info
        +
        +
        +property album_info

        return list of AlbumInfo objects for each album in the photos database

        -
        -
        -property album_info_shared
        +
        +
        +property album_info_shared

        return list of AlbumInfo objects for each shared album in the photos database only valid for Photos 5; on Photos <= 4, prints warning and returns empty list

        -
        -
        -property albums
        +
        +
        +property albums

        return list of albums found in photos database

        -
        -
        -property albums_as_dict
        +
        +
        +property albums_as_dict

        return albums as dict of albums, count in reverse sorted order (descending)

        -
        -
        -property albums_shared
        +
        +
        +property albums_shared

        return list of shared albums found in photos database only valid for Photos 5; on Photos <= 4, prints warning and returns empty list

        -
        -
        -property albums_shared_as_dict
        +
        +
        +property albums_shared_as_dict

        returns shared albums as dict of albums, count in reverse sorted order (descending) valid only on Photos 5; on Photos <= 4, prints warning and returns empty dict

        -
        -
        -property db_path
        +
        +
        +property db_path

        returns path to the Photos library database PhotosDB was initialized with

        -
        -
        -property db_version
        +
        +
        +property db_version

        return the database version as stored in LiGlobals table

        -
        -
        -property folder_info
        +
        +
        +property folder_info

        return list FolderInfo objects representing top-level folders in the photos database

        -
        -
        -property folders
        +
        +
        +property folders

        return list of top-level folder names in the photos database

        -
        -get_db_connection()[source]
        +
        +get_db_connection()[source]

        Get connection to the working copy of the Photos database

        Returns
        @@ -114,8 +114,8 @@ valid only on Photos 5; on Photos <= 4, prints warning and returns empty dict
        -
        -get_photo(uuid)[source]
        +
        +get_photo(uuid)[source]

        Returns a single photo matching uuid

        Parameters
        @@ -127,33 +127,33 @@ valid only on Photos 5; on Photos <= 4, prints warning and returns empty dict
        -
        -
        -property import_info
        +
        +
        +property import_info

        return list of ImportInfo objects for each import session in the database

        -
        -
        -property keywords
        +
        +
        +property keywords

        return list of keywords found in photos database

        -
        -
        -property keywords_as_dict
        +
        +
        +property keywords_as_dict

        return keywords as dict of keyword, count in reverse sorted order (descending)

        -
        -
        -property labels
        +
        +
        +property labels

        return list of all search info labels found in the library

        -
        -
        -property labels_as_dict
        +
        +
        +property labels_as_dict

        count in reverse sorted order (descending)

        Type
        @@ -162,15 +162,15 @@ valid only on Photos 5; on Photos <= 4, prints warning and returns empty dict
        -
        -
        -property labels_normalized
        +
        +
        +property labels_normalized

        return list of all normalized search info labels found in the library

        -
        -
        -property labels_normalized_as_dict
        +
        +
        +property labels_normalized_as_dict

        count in reverse sorted order (descending)

        Type
        @@ -179,33 +179,33 @@ valid only on Photos 5; on Photos <= 4, prints warning and returns empty dict
        -
        -
        -property library_path
        +
        +
        +property library_path

        returns path to the Photos library PhotosDB was initialized with

        -
        -
        -property person_info
        +
        +
        +property person_info

        return list of PersonInfo objects for each person in the photos database

        -
        -
        -property persons
        +
        +
        +property persons

        return list of persons found in photos database

        -
        -
        -property persons_as_dict
        +
        +
        +property persons_as_dict

        return persons as dict of person, count in reverse sorted order (descending)

        -
        -photos(keywords=None, uuid=None, persons=None, albums=None, images=True, movies=True, from_date=None, to_date=None, intrash=False)[source]
        +
        +photos(keywords=None, uuid=None, persons=None, albums=None, images=True, movies=True, from_date=None, to_date=None, intrash=False)[source]

        Return a list of PhotoInfo objects If called with no args, returns the entire database of photos If called with args, returns photos matching the args (e.g. keywords, persons, etc.) @@ -236,8 +236,8 @@ if False returns only photos that aren’t deleted; default is False

        -
        -photos_by_uuid(uuids)[source]
        +
        +photos_by_uuid(uuids)[source]
        Returns a list of photos with UUID in uuids.

        Does not generate error if invalid or missing UUID passed. This is faster than using PhotosDB.photos if you have list of UUIDs. @@ -255,8 +255,8 @@ Returns photos regardless of intrash state.

        -
        -query(options: osxphotos.queryoptions.QueryOptions)List[osxphotos.photoinfo.photoinfo.PhotoInfo][source]
        +
        +query(options: osxphotos.queryoptions.QueryOptions)List[osxphotos.photoinfo.photoinfo.PhotoInfo][source]

        Run a query against PhotosDB to extract the photos based on user supplied options

        Parameters
        @@ -268,498 +268,504 @@ Returns photos regardless of intrash state.

        -
        -class osxphotos.PhotoInfo(db=None, uuid=None, info=None)[source]
        +
        +class osxphotos.PhotoInfo(db=None, uuid=None, info=None)[source]

        Info about a specific photo, contains all the details about the photo including keywords, persons, albums, uuid, path, etc.

        -
        -class ExifInfo(flash_fired: bool, iso: int, metering_mode: int, sample_rate: int, track_format: int, white_balance: int, aperture: float, bit_rate: float, duration: float, exposure_bias: float, focal_length: float, fps: float, latitude: float, longitude: float, shutter_speed: float, camera_make: str, camera_model: str, codec: str, lens_model: str)
        +
        +class ExifInfo(flash_fired: bool, iso: int, metering_mode: int, sample_rate: int, track_format: int, white_balance: int, aperture: float, bit_rate: float, duration: float, exposure_bias: float, focal_length: float, fps: float, latitude: float, longitude: float, shutter_speed: float, camera_make: str, camera_model: str, codec: str, lens_model: str)

        EXIF info associated with a photo from the Photos library

        -
        -aperture: float
        +
        +aperture: float
        -
        -bit_rate: float
        +
        +bit_rate: float
        -
        -camera_make: str
        +
        +camera_make: str
        -
        -camera_model: str
        +
        +camera_model: str
        -
        -codec: str
        +
        +codec: str
        -
        -duration: float
        +
        +duration: float
        -
        -exposure_bias: float
        +
        +exposure_bias: float
        -
        -flash_fired: bool
        +
        +flash_fired: bool
        -
        -focal_length: float
        +
        +focal_length: float
        -
        -fps: float
        +
        +fps: float
        -
        -iso: int
        +
        +iso: int
        -
        -latitude: float
        +
        +latitude: float
        -
        -lens_model: str
        +
        +lens_model: str
        -
        -longitude: float
        +
        +longitude: float
        -
        -metering_mode: int
        +
        +metering_mode: int
        -
        -sample_rate: int
        +
        +sample_rate: int
        -
        -shutter_speed: float
        +
        +shutter_speed: float
        -
        -track_format: int
        +
        +track_format: int
        -
        -white_balance: int
        +
        +white_balance: int
        -
        -class ExportResults(exported=None, new=None, updated=None, skipped=None, exif_updated=None, touched=None, converted_to_jpeg=None, sidecar_json_written=None, sidecar_json_skipped=None, sidecar_exiftool_written=None, sidecar_exiftool_skipped=None, sidecar_xmp_written=None, sidecar_xmp_skipped=None, missing=None, error=None, exiftool_warning=None, exiftool_error=None, xattr_written=None, xattr_skipped=None, deleted_files=None, deleted_directories=None, exported_album=None, skipped_album=None, missing_album=None)
        +
        +class ExportResults(exported=None, new=None, updated=None, skipped=None, exif_updated=None, touched=None, converted_to_jpeg=None, sidecar_json_written=None, sidecar_json_skipped=None, sidecar_exiftool_written=None, sidecar_exiftool_skipped=None, sidecar_xmp_written=None, sidecar_xmp_skipped=None, missing=None, error=None, exiftool_warning=None, exiftool_error=None, xattr_written=None, xattr_skipped=None, deleted_files=None, deleted_directories=None, exported_album=None, skipped_album=None, missing_album=None)

        holds export results for export2

        -
        -all_files()
        +
        +all_files()

        return all filenames contained in results

        -
        -class ScoreInfo(overall: float, curation: float, promotion: float, highlight_visibility: float, behavioral: float, failure: float, harmonious_color: float, immersiveness: float, interaction: float, interesting_subject: float, intrusive_object_presence: float, lively_color: float, low_light: float, noise: float, pleasant_camera_tilt: float, pleasant_composition: float, pleasant_lighting: float, pleasant_pattern: float, pleasant_perspective: float, pleasant_post_processing: float, pleasant_reflection: float, pleasant_symmetry: float, sharply_focused_subject: float, tastefully_blurred: float, well_chosen_subject: float, well_framed_subject: float, well_timed_shot: float)
        +
        +class ScoreInfo(overall: float, curation: float, promotion: float, highlight_visibility: float, behavioral: float, failure: float, harmonious_color: float, immersiveness: float, interaction: float, interesting_subject: float, intrusive_object_presence: float, lively_color: float, low_light: float, noise: float, pleasant_camera_tilt: float, pleasant_composition: float, pleasant_lighting: float, pleasant_pattern: float, pleasant_perspective: float, pleasant_post_processing: float, pleasant_reflection: float, pleasant_symmetry: float, sharply_focused_subject: float, tastefully_blurred: float, well_chosen_subject: float, well_framed_subject: float, well_timed_shot: float)

        Computed photo score info associated with a photo from the Photos library

        -
        -behavioral: float
        +
        +behavioral: float
        -
        -curation: float
        +
        +curation: float
        -
        -failure: float
        +
        +failure: float
        -
        -harmonious_color: float
        +
        +harmonious_color: float
        -
        -highlight_visibility: float
        +
        +highlight_visibility: float
        -
        -immersiveness: float
        +
        +immersiveness: float
        -
        -interaction: float
        +
        +interaction: float
        -
        -interesting_subject: float
        +
        +interesting_subject: float
        -
        -intrusive_object_presence: float
        +
        +intrusive_object_presence: float
        -
        -lively_color: float
        +
        +lively_color: float
        -
        -low_light: float
        +
        +low_light: float
        -
        -noise: float
        +
        +noise: float
        -
        -overall: float
        +
        +overall: float
        -
        -pleasant_camera_tilt: float
        +
        +pleasant_camera_tilt: float
        -
        -pleasant_composition: float
        +
        +pleasant_composition: float
        -
        -pleasant_lighting: float
        +
        +pleasant_lighting: float
        -
        -pleasant_pattern: float
        +
        +pleasant_pattern: float
        -
        -pleasant_perspective: float
        +
        +pleasant_perspective: float
        -
        -pleasant_post_processing: float
        +
        +pleasant_post_processing: float
        -
        -pleasant_reflection: float
        +
        +pleasant_reflection: float
        -
        -pleasant_symmetry: float
        +
        +pleasant_symmetry: float
        -
        -promotion: float
        +
        +promotion: float
        -
        -sharply_focused_subject: float
        +
        +sharply_focused_subject: float
        -
        -tastefully_blurred: float
        +
        +tastefully_blurred: float
        -
        -well_chosen_subject: float
        +
        +well_chosen_subject: float
        -
        -well_framed_subject: float
        +
        +well_framed_subject: float
        -
        -well_timed_shot: float
        +
        +well_timed_shot: float
        -
        -class SearchInfo(photo, normalized=False)
        +
        +class SearchInfo(photo, normalized=False)

        Info about search terms such as machine learning labels that Photos knows about a photo

        -
        -
        -property activities
        +
        +
        +property activities

        returns list of activity names

        -
        -
        -property all
        +
        +
        +property all

        return all search info properties in a single list

        -
        -asdict()
        +
        +asdict()

        return dict of search info

        -
        -
        -property bodies_of_water
        +
        +
        +property bodies_of_water

        returns list of body of water names

        -
        -
        -property city
        +
        +
        +property city

        returns city/town

        -
        -
        -property country
        +
        +
        +property country

        returns country name

        -
        -
        -property holidays
        +
        +
        +property holidays

        returns list of holiday names

        -
        -
        -property labels
        +
        +
        +property labels

        return list of labels associated with Photo

        -
        -
        -property locality_names
        +
        +
        +property locality_names

        returns list of other locality names

        -
        -
        -property media_types
        +
        +
        +property media_types

        returns list of media types (photo, video, panorama, etc)

        -
        -
        -property month
        +
        +
        +property month

        returns month name

        -
        -
        -property neighborhoods
        +
        +
        +property neighborhoods

        returns list of neighborhoods

        -
        -
        -property place_names
        +
        +
        +property place_names

        returns list of place names

        -
        -
        -property season
        +
        +
        +property season

        returns season name

        -
        -
        -property state
        +
        +
        +property state

        returns state name

        -
        -
        -property state_abbreviation
        +
        +
        +property state_abbreviation

        returns state abbreviation

        -
        -
        -property streets
        +
        +
        +property streets

        returns list of street names

        -
        -
        -property venue_types
        +
        +
        +property venue_types

        returns list of venue types

        -
        -
        -property venues
        +
        +
        +property venues

        returns list of venue names

        -
        -
        -property year
        +
        +
        +property year

        returns year

        -
        -
        -property adjustments
        +
        +
        +property adjustments

        Returns AdjustmentsInfo class for adjustment data or None if no adjustments; Photos 5+ only

        -
        -
        -property album_info
        +
        +
        +property album_info

        list of AlbumInfo objects representing albums the photo is contained in

        -
        -
        -property albums
        +
        +
        +property albums

        list of albums picture is contained in

        -
        -asdict()[source]
        +
        +asdict()[source]

        return dict representation

        -
        -
        -property burst
        +
        +
        +property burst

        Returns True if photo is part of a Burst photo set, otherwise False

        -
        -
        -property burst_album_info
        +
        +
        +property burst_album_info

        If photo is a burst photo, returns list of AlbumInfo objects representing albums the photo is contained in as well as albums the burst key photo is contained in, otherwise returns self.album_info.

        -
        -
        -property burst_albums
        +
        +
        +property burst_albums

        If photo is burst photo, list of albums it is contained in as well as any albums the key photo is contained in, otherwise returns self.albums

        -
        -
        -property burst_default_pick
        +
        +
        +property burst_default_pick

        Returns True if photo is a burst image and is the photo that Photos selected as the default image for the burst set, otherwise False

        -
        -
        -property burst_key
        +
        +
        +property burst_key

        Returns True if photo is a burst photo and is the key image for the burst set (the image that Photos shows on top of the burst stack), otherwise False

        -
        -
        -property burst_photos
        +
        +
        +property burst_photos

        If photo is a burst photo, returns list of PhotoInfo objects that are part of the same burst photo set; otherwise returns empty list. self is not included in the returned list

        -
        -
        -property burst_selected
        +
        +
        +property burst_selected

        Returns True if photo is a burst photo and has been selected from the burst set by the user, otherwise False

        -
        -
        -property comments
        +
        +
        +property comments

        Returns list of Comment objects for any comments on the photo (sorted by date)

        -
        -
        -property date
        +
        +
        +property date

        image creation date as timezone aware datetime object

        -
        -
        -property date_added
        +
        +
        +property date_added

        Date photo was added to the database

        -
        -
        -property date_modified
        +
        +
        +property date_modified

        image modification date as timezone aware datetime object or None if no modification date set

        -
        -
        -property date_trashed
        +
        +
        +property date_trashed

        Date asset was placed in the trash or None

        -
        -
        -property description
        +
        +
        +property description

        long / extended description of picture

        -
        -
        -property exif_info
        +
        +
        +property duplicates
        +

        return list of PhotoInfo objects for possible duplicates (matching signature of original size, date, height, width) or empty list if no matching duplicates

        +
        + +
        +
        +property exif_info

        Returns an ExifInfo object with the EXIF data for photo Note: the returned EXIF data is the data Photos stores in the database on import; ExifInfo does not provide access to the EXIF info in the actual image file @@ -767,18 +773,18 @@ Some or all of the fields may be None Only valid for Photos 5; on earlier database returns None

        -
        -
        -property exiftool
        -

        Returns an ExifTool object for the photo -requires that exiftool (https://exiftool.org/) be installed +

        +
        +property exiftool
        +

        Returns a ExifToolCaching (read-only instance of ExifTool) object for the photo. +Requires that exiftool (https://exiftool.org/) be installed If exiftool not installed, logs warning and returns None If photo path is missing, returns None

        -
        -export(dest, *filename, edited=False, live_photo=False, raw_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, keyword_template=None, description_template=None)
        +
        +export(dest, filename=None, edited=False, live_photo=False, raw_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, keyword_template=None, description_template=None, render_options: Optional[osxphotos.phototemplate.RenderOptions] = None)

        export photo dest: must be valid destination path (or exception raised) filename: (optional): name of exported picture; if not provided, will use current filename

        @@ -794,13 +800,13 @@ 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

        (or raise exception if no edited version)

        +
        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

        @@ -822,13 +828,14 @@ when exporting metadata with exiftool or sidecar use_persons_as_keywords: (boolean, default = False); if True, will include person names in keywords when exporting metadata with exiftool or sidecar keyword_template: (list of strings); list of template strings that will be rendered as used as keywords -description_template: string; optional template string that will be rendered for use as photo description

        +description_template: string; optional template string that will be rendered for use as photo description +render_options: an optional osxphotos.phototemplate.RenderOptions instance with options to pass to template renderer

        Returns: list of photos exported

        -
        -export2(dest, *filename, edited=False, live_photo=False, raw_photo=False, export_as_hardlink=False, overwrite=False, increment=True, sidecar=0, sidecar_drop_ext=False, use_photos_export=False, timeout=120, exiftool=False, use_albums_as_keywords=False, use_persons_as_keywords=False, keyword_template=None, description_template=None, update=False, ignore_signature=False, export_db=None, fileutil=<class 'osxphotos.fileutil.FileUtil'>, dry_run=False, touch_file=False, convert_to_jpeg=False, jpeg_quality=1.0, ignore_date_modified=False, use_photokit=False, verbose=None, exiftool_flags=None, merge_exif_keywords=False, merge_exif_persons=False, jpeg_ext=None, persons=True, location=True, replace_keywords=False)
        +
        +export2(dest, original=True, original_filename=None, edited=False, edited_filename=None, live_photo=False, raw_photo=False, export_as_hardlink=False, overwrite=False, increment=True, sidecar=0, sidecar_drop_ext=False, use_photos_export=False, timeout=120, exiftool=False, use_albums_as_keywords=False, use_persons_as_keywords=False, keyword_template=None, description_template=None, update=False, ignore_signature=False, export_db=None, fileutil=<class 'osxphotos.fileutil.FileUtil'>, dry_run=False, touch_file=False, convert_to_jpeg=False, jpeg_quality=1.0, ignore_date_modified=False, use_photokit=False, verbose=None, exiftool_flags=None, merge_exif_keywords=False, merge_exif_persons=False, jpeg_ext=None, persons=True, location=True, replace_keywords=False, preview=False, preview_suffix='_preview', render_options: Optional[osxphotos.phototemplate.RenderOptions] = None)

        export photo, like export but with update and dry_run options dest: must be valid destination path or exception raised filename: (optional): name of exported picture; if not provided, will use current filename

        @@ -841,14 +848,12 @@ 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

        -
        -
        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 associted .mov for live photos -raw_photo: (boolean, default=False); if True, will also export the associted RAW photo +

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

        @@ -896,7 +901,10 @@ merge_exif_persons: boolean; if True, merged persons found in file’s exif data jpeg_ext: if set, will use this value for extension on jpegs converted to jpeg with convert_to_jpeg; if not set, uses jpeg; do not include the leading “.” 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

        +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 +render_options: optional osxphotos.phototemplate.RenderOptions instance to specify options for rendering templates

        Returns: ExportResults class

        ExportResults has attributes: “exported”, @@ -923,90 +931,90 @@ replace_keywords: if True, keyword_template replaces any keywords, otherwise it

        -
        -
        -property external_edit
        +
        +
        +property external_edit

        Returns True if picture was edited outside of Photos using external editor

        -
        -
        -property face_info
        +
        +
        +property face_info

        list of FaceInfo objects for faces in picture

        -
        -
        -property favorite
        +
        +
        +property favorite

        True if picture is marked as favorite

        -
        -
        -property filename
        +
        +
        +property filename

        filename of the picture

        -
        -
        -property has_raw
        +
        +
        +property has_raw

        returns True if photo has an associated raw image (that is, it’s a RAW+JPEG pair), otherwise False

        -
        -
        -property hasadjustments
        +
        +
        +property hasadjustments

        True if picture has adjustments / edits

        -
        -
        -property hdr
        +
        +
        +property hdr

        Returns True if photo is an HDR photo, otherwise False

        -
        -
        -property height
        +
        +
        +property height

        returns height of the current photo version in pixels

        -
        -
        -property hidden
        +
        +
        +property hidden

        True if picture is hidden

        -
        -
        -property import_info
        +
        +
        +property import_info

        ImportInfo object representing import session for the photo or None if no import session

        -
        -
        -property incloud
        +
        +
        +property incloud

        Returns True if photo is cloud asset and is synched to cloud False if photo is cloud asset and not yet synched to cloud None if photo is not cloud asset

        -
        -
        -property intrash
        +
        +
        +property intrash

        True if picture is in trash (‘Recently Deleted’ folder)

        -
        -
        -property iscloudasset
        +
        +
        +property iscloudasset

        Returns True if photo is a cloud asset (in an iCloud library), otherwise False

        -
        -
        -property ismissing
        +
        +
        +property ismissing

        returns true if photo is missing from disk (which means it’s not been downloaded from iCloud) NOTE: the photos.db database uses an asynchrounous write-ahead log so changes in Photos

        @@ -1019,200 +1027,196 @@ isMissing = 1

        -
        -
        -property ismovie
        +
        +
        +property ismovie

        Returns True if file is a movie, otherwise False

        -
        -
        -property isphoto
        +
        +
        +property isphoto

        Returns True if file is an image, otherwise False

        -
        -
        -property israw
        +
        +
        +property israw

        returns True if photo is a raw image. For images with an associated RAW+JPEG pair, see has_raw

        -
        -
        -property isreference
        +
        +
        +property isreference

        Returns True if photo is a reference (not copied to the Photos library), otherwise False

        -
        -json()[source]
        +
        +json()[source]

        Return JSON representation

        -
        -
        -property keywords
        +
        +
        +property keywords

        list of keywords for picture

        -
        -
        -property labels
        +
        +
        +property labels

        returns list of labels applied to photo by Photos image categorization only valid on Photos 5, on older libraries returns empty list

        -
        -
        -property labels_normalized
        +
        +
        +property labels_normalized

        returns normalized list of labels applied to photo by Photos image categorization only valid on Photos 5, on older libraries returns empty list

        -
        -
        -property likes
        +
        +
        +property likes

        Returns list of Like objects for any likes on the photo (sorted by date)

        -
        -
        -property live_photo
        +
        +
        +property live_photo

        Returns True if photo is a live photo, otherwise False

        -
        -
        -property location
        +
        +
        +property location

        returns (latitude, longitude) as float in degrees or None

        -
        -
        -property orientation
        +
        +
        +property orientation

        returns EXIF orientation of the current photo version as int or 0 if current orientation cannot be determined

        -
        -
        -property original_filename
        +
        +
        +property original_filename

        original filename of the picture Photos 5 mangles filenames upon import

        -
        -
        -property original_filesize
        +
        +
        +property original_filesize

        returns filesize of original photo in bytes as int

        -
        -
        -property original_height
        +
        +
        +property original_height

        returns height of the original photo version in pixels

        -
        -
        -property original_orientation
        +
        +
        +property original_orientation

        returns EXIF orientation of the original photo version as int

        -
        -
        -property original_width
        +
        +
        +property original_width

        returns width of the original photo version in pixels

        -
        -
        -property panorama
        +
        +
        +property panorama

        Returns True if photo is a panorama, otherwise False

        -
        -
        -property path
        +
        +
        +property path

        absolute path on disk of the original picture

        -
        -
        -property path_derivatives
        +
        +
        +property path_derivatives

        Return any derivative (preview) images associated with the photo as a list of paths, sorted by file size (largest first)

        -
        -
        -property path_edited
        +
        +
        +property path_edited

        absolute path on disk of the edited picture

        -
        -
        -property path_live_photo
        +
        +
        +property path_edited_live_photo
        +

        return path to edited version of live photo movie; only valid for Photos 5+

        +
        + +
        +
        +property path_live_photo

        Returns path to the associated video file for a live photo If photo is not a live photo, returns None If photo is missing, returns None

        -
        -
        -property path_raw
        +
        +
        +property path_raw

        absolute path of associated RAW image or None if there is not one

        -
        -
        -property person_info
        +
        +
        +property person_info

        list of PersonInfo objects for person in picture

        -
        -
        -property persons
        +
        +
        +property persons

        list of persons in picture

        -
        -
        -property place
        +
        +
        +property place

        Returns PlaceInfo object containing reverse geolocation info

        -
        -
        -property portrait
        +
        +
        +property portrait

        Returns True if photo is a portrait, otherwise False

        -
        -
        -property raw_original
        +
        +
        +property raw_original

        returns True if associated raw image and the raw image is selected in Photos via “Use RAW as Original ” otherwise returns False

        -
        -render_template(template_str, none_str='_', path_sep=None, expand_inplace=False, inplace_sep=None, filename=False, dirname=False, strip=False, edited=False)[source]
        +
        +render_template(template_str: str, options: Optional[osxphotos.phototemplate.RenderOptions] = None)[source]

        Renders a template string for PhotoInfo instance using PhotoTemplate

        Parameters
        • template_str – a template string with fields to render

        • -
        • none_str – a str to use if template field renders to None, default is “_”.

        • -
        • path_sep – a single character str to use as path separator when joining -fields like folder_album; if not provided, defaults to os.path.sep

        • -
        • expand_inplace – expand multi-valued substitutions in-place as a single string -instead of returning individual strings

        • -
        • inplace_sep – optional string to use as separator between multi-valued keywords -with expand_inplace; default is ‘,’

        • -
        • filename – if True, template output will be sanitized to produce valid file name

        • -
        • dirname – if True, template output will be sanitized to produce valid directory name

        • -
        • strip – if True, strips leading/trailing white space from resulting template

        • -
        • edited – if True, sets {edited_version} field to True, otherwise it gets set to False; set if you want template evaluated for edited version

        • +
        • options – a RenderOptions instance

        Returns
        @@ -1224,9 +1228,9 @@ with expand_inplace; default is ‘,’

        -
        -
        -property score
        +
        +
        +property score

        Computed score information for a photo

        Returns
        @@ -1235,108 +1239,108 @@ with expand_inplace; default is ‘,’

        -
        -
        -property screenshot
        +
        +
        +property screenshot

        Returns True if photo is an HDR photo, otherwise False

        -
        -
        -property search_info
        +
        +
        +property search_info

        returns SearchInfo object for photo only valid on Photos 5, on older libraries, returns None

        -
        -
        -property search_info_normalized
        +
        +
        +property search_info_normalized

        returns SearchInfo object for photo that produces normalized results only valid on Photos 5, on older libraries, returns None

        -
        -
        -property selfie
        +
        +
        +property selfie

        Returns True if photo is a selfie (front facing camera), otherwise False

        -
        -
        -property shared
        +
        +
        +property shared

        returns True if photos is in a shared iCloud album otherwise false Only valid on Photos 5; returns None on older versions

        -
        -
        -property slow_mo
        +
        +
        +property slow_mo

        Returns True if photo is a slow motion video, otherwise False

        -
        -
        -property time_lapse
        +
        +
        +property time_lapse

        Returns True if photo is a time lapse video, otherwise False

        -
        -
        -property title
        +
        +
        +property title

        name / title of picture

        -
        -
        -property tzoffset
        +
        +
        +property tzoffset

        timezone offset from UTC in seconds

        -
        -
        -property uti
        +
        +
        +property uti

        Returns Uniform Type Identifier (UTI) for the image for example: public.jpeg or com.apple.quicktime-movie

        -
        -
        -property uti_edited
        +
        +
        +property uti_edited

        Returns Uniform Type Identifier (UTI) for the edited image if the photo has been edited, otherwise None; for example: public.jpeg

        -
        -
        -property uti_original
        +
        +
        +property uti_original

        Returns Uniform Type Identifier (UTI) for the original image for example: public.jpeg or com.apple.quicktime-movie

        -
        -
        -property uti_raw
        +
        +
        +property uti_raw

        Returns Uniform Type Identifier (UTI) for the RAW image if there is one for example: com.canon.cr2-raw-image Returns None if no associated RAW image

        -
        -
        -property uuid
        +
        +
        +property uuid

        UUID of picture

        -
        -
        -property visible
        +
        +
        +property visible

        True if picture is visble

        -
        -
        -property width
        +
        +
        +property width

        returns width of the current photo version in pixels

        @@ -1404,7 +1408,7 @@ Returns None if no associated RAW image

        ©2021, Rhet Turnbull. | - Powered by Sphinx 3.5.2 + Powered by Sphinx 4.0.2 & Alabaster 0.7.12 | diff --git a/docs/search.html b/docs/search.html index 4786c9c7..41d6c13c 100644 --- a/docs/search.html +++ b/docs/search.html @@ -5,11 +5,11 @@ - Search — osxphotos 0.42.20 documentation - - + Search — osxphotos 0.42.66 documentation + + - + @@ -37,6 +37,7 @@

        Search

        +

        @@ -44,19 +45,26 @@ functionality.

        + +

        Searching for multiple words only shows matches that contain all words.

        + +
        + +
        +
        @@ -102,7 +110,7 @@ ©2021, Rhet Turnbull. | - Powered by Sphinx 3.5.2 + Powered by Sphinx 4.0.2 & Alabaster 0.7.12 diff --git a/docs/searchindex.js b/docs/searchindex.js index 9bb00162..3d5c31b1 100644 --- a/docs/searchindex.js +++ b/docs/searchindex.js @@ -1 +1 @@ -Search.setIndex({docnames:["cli","index","modules","reference","tutorial"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.intersphinx":1,"sphinx.ext.viewcode":1,sphinx:56},filenames:["cli.rst","index.rst","modules.rst","reference.rst","tutorial.md"],objects:{"osxphotos-albums":{"--db":[0,3,1,"cmdoption-osxphotos-albums-db"],"--json":[0,3,1,"cmdoption-osxphotos-albums-json"],PHOTOS_LIBRARY:[0,3,1,"cmdoption-osxphotos-albums-arg-PHOTOS_LIBRARY"]},"osxphotos-dump":{"--db":[0,3,1,"cmdoption-osxphotos-dump-db"],"--deleted":[0,3,1,"cmdoption-osxphotos-dump-deleted"],"--deleted-only":[0,3,1,"cmdoption-osxphotos-dump-deleted-only"],"--json":[0,3,1,"cmdoption-osxphotos-dump-json"],PHOTOS_LIBRARY:[0,3,1,"cmdoption-osxphotos-dump-arg-PHOTOS_LIBRARY"]},"osxphotos-export":{"--add-exported-to-album":[0,3,1,"cmdoption-osxphotos-export-add-exported-to-album"],"--add-missing-to-album":[0,3,1,"cmdoption-osxphotos-export-add-missing-to-album"],"--add-skipped-to-album":[0,3,1,"cmdoption-osxphotos-export-add-skipped-to-album"],"--album":[0,3,1,"cmdoption-osxphotos-export-album"],"--album-keyword":[0,3,1,"cmdoption-osxphotos-export-album-keyword"],"--burst":[0,3,1,"cmdoption-osxphotos-export-burst"],"--cleanup":[0,3,1,"cmdoption-osxphotos-export-cleanup"],"--convert-to-jpeg":[0,3,1,"cmdoption-osxphotos-export-convert-to-jpeg"],"--current-name":[0,3,1,"cmdoption-osxphotos-export-current-name"],"--db":[0,3,1,"cmdoption-osxphotos-export-db"],"--deleted":[0,3,1,"cmdoption-osxphotos-export-deleted"],"--deleted-only":[0,3,1,"cmdoption-osxphotos-export-deleted-only"],"--description":[0,3,1,"cmdoption-osxphotos-export-description"],"--description-template":[0,3,1,"cmdoption-osxphotos-export-description-template"],"--directory":[0,3,1,"cmdoption-osxphotos-export-directory"],"--download-missing":[0,3,1,"cmdoption-osxphotos-export-download-missing"],"--dry-run":[0,3,1,"cmdoption-osxphotos-export-dry-run"],"--edited":[0,3,1,"cmdoption-osxphotos-export-edited"],"--edited-suffix":[0,3,1,"cmdoption-osxphotos-export-edited-suffix"],"--exiftool":[0,3,1,"cmdoption-osxphotos-export-exiftool"],"--exiftool-merge-keywords":[0,3,1,"cmdoption-osxphotos-export-exiftool-merge-keywords"],"--exiftool-merge-persons":[0,3,1,"cmdoption-osxphotos-export-exiftool-merge-persons"],"--exiftool-option":[0,3,1,"cmdoption-osxphotos-export-exiftool-option"],"--exiftool-path":[0,3,1,"cmdoption-osxphotos-export-exiftool-path"],"--export-as-hardlink":[0,3,1,"cmdoption-osxphotos-export-export-as-hardlink"],"--export-by-date":[0,3,1,"cmdoption-osxphotos-export-export-by-date"],"--exportdb":[0,3,1,"cmdoption-osxphotos-export-exportdb"],"--external-edit":[0,3,1,"cmdoption-osxphotos-export-external-edit"],"--favorite":[0,3,1,"cmdoption-osxphotos-export-favorite"],"--filename":[0,3,1,"cmdoption-osxphotos-export-filename"],"--finder-tag-keywords":[0,3,1,"cmdoption-osxphotos-export-finder-tag-keywords"],"--finder-tag-template":[0,3,1,"cmdoption-osxphotos-export-finder-tag-template"],"--folder":[0,3,1,"cmdoption-osxphotos-export-folder"],"--from-date":[0,3,1,"cmdoption-osxphotos-export-from-date"],"--from-time":[0,3,1,"cmdoption-osxphotos-export-from-time"],"--has-comment":[0,3,1,"cmdoption-osxphotos-export-has-comment"],"--has-likes":[0,3,1,"cmdoption-osxphotos-export-has-likes"],"--has-raw":[0,3,1,"cmdoption-osxphotos-export-has-raw"],"--hdr":[0,3,1,"cmdoption-osxphotos-export-hdr"],"--hidden":[0,3,1,"cmdoption-osxphotos-export-hidden"],"--ignore-case":[0,3,1,"cmdoption-osxphotos-export-i"],"--ignore-date-modified":[0,3,1,"cmdoption-osxphotos-export-ignore-date-modified"],"--ignore-signature":[0,3,1,"cmdoption-osxphotos-export-ignore-signature"],"--in-album":[0,3,1,"cmdoption-osxphotos-export-in-album"],"--is-reference":[0,3,1,"cmdoption-osxphotos-export-is-reference"],"--jpeg-ext":[0,3,1,"cmdoption-osxphotos-export-jpeg-ext"],"--jpeg-quality":[0,3,1,"cmdoption-osxphotos-export-jpeg-quality"],"--keyword":[0,3,1,"cmdoption-osxphotos-export-keyword"],"--keyword-template":[0,3,1,"cmdoption-osxphotos-export-keyword-template"],"--label":[0,3,1,"cmdoption-osxphotos-export-label"],"--live":[0,3,1,"cmdoption-osxphotos-export-live"],"--load-config":[0,3,1,"cmdoption-osxphotos-export-load-config"],"--max-size":[0,3,1,"cmdoption-osxphotos-export-max-size"],"--min-size":[0,3,1,"cmdoption-osxphotos-export-min-size"],"--missing":[0,3,1,"cmdoption-osxphotos-export-missing"],"--name":[0,3,1,"cmdoption-osxphotos-export-name"],"--no-comment":[0,3,1,"cmdoption-osxphotos-export-no-comment"],"--no-description":[0,3,1,"cmdoption-osxphotos-export-no-description"],"--no-likes":[0,3,1,"cmdoption-osxphotos-export-no-likes"],"--no-place":[0,3,1,"cmdoption-osxphotos-export-no-place"],"--no-title":[0,3,1,"cmdoption-osxphotos-export-no-title"],"--not-burst":[0,3,1,"cmdoption-osxphotos-export-not-burst"],"--not-favorite":[0,3,1,"cmdoption-osxphotos-export-not-favorite"],"--not-hdr":[0,3,1,"cmdoption-osxphotos-export-not-hdr"],"--not-hidden":[0,3,1,"cmdoption-osxphotos-export-not-hidden"],"--not-in-album":[0,3,1,"cmdoption-osxphotos-export-not-in-album"],"--not-live":[0,3,1,"cmdoption-osxphotos-export-not-live"],"--not-panorama":[0,3,1,"cmdoption-osxphotos-export-not-panorama"],"--not-portrait":[0,3,1,"cmdoption-osxphotos-export-not-portrait"],"--not-screenshot":[0,3,1,"cmdoption-osxphotos-export-not-screenshot"],"--not-selfie":[0,3,1,"cmdoption-osxphotos-export-not-selfie"],"--not-shared":[0,3,1,"cmdoption-osxphotos-export-not-shared"],"--not-slow-mo":[0,3,1,"cmdoption-osxphotos-export-not-slow-mo"],"--not-time-lapse":[0,3,1,"cmdoption-osxphotos-export-not-time-lapse"],"--only-movies":[0,3,1,"cmdoption-osxphotos-export-only-movies"],"--only-new":[0,3,1,"cmdoption-osxphotos-export-only-new"],"--only-photos":[0,3,1,"cmdoption-osxphotos-export-only-photos"],"--original-suffix":[0,3,1,"cmdoption-osxphotos-export-original-suffix"],"--overwrite":[0,3,1,"cmdoption-osxphotos-export-overwrite"],"--panorama":[0,3,1,"cmdoption-osxphotos-export-panorama"],"--person":[0,3,1,"cmdoption-osxphotos-export-person"],"--person-keyword":[0,3,1,"cmdoption-osxphotos-export-person-keyword"],"--place":[0,3,1,"cmdoption-osxphotos-export-place"],"--portrait":[0,3,1,"cmdoption-osxphotos-export-portrait"],"--query-eval":[0,3,1,"cmdoption-osxphotos-export-query-eval"],"--regex":[0,3,1,"cmdoption-osxphotos-export-regex"],"--replace-keywords":[0,3,1,"cmdoption-osxphotos-export-replace-keywords"],"--report":[0,3,1,"cmdoption-osxphotos-export-report"],"--retry":[0,3,1,"cmdoption-osxphotos-export-retry"],"--save-config":[0,3,1,"cmdoption-osxphotos-export-save-config"],"--screenshot":[0,3,1,"cmdoption-osxphotos-export-screenshot"],"--selfie":[0,3,1,"cmdoption-osxphotos-export-selfie"],"--shared":[0,3,1,"cmdoption-osxphotos-export-shared"],"--sidecar":[0,3,1,"cmdoption-osxphotos-export-sidecar"],"--sidecar-drop-ext":[0,3,1,"cmdoption-osxphotos-export-sidecar-drop-ext"],"--skip-bursts":[0,3,1,"cmdoption-osxphotos-export-skip-bursts"],"--skip-edited":[0,3,1,"cmdoption-osxphotos-export-skip-edited"],"--skip-live":[0,3,1,"cmdoption-osxphotos-export-skip-live"],"--skip-original-if-edited":[0,3,1,"cmdoption-osxphotos-export-skip-original-if-edited"],"--skip-raw":[0,3,1,"cmdoption-osxphotos-export-skip-raw"],"--slow-mo":[0,3,1,"cmdoption-osxphotos-export-slow-mo"],"--strip":[0,3,1,"cmdoption-osxphotos-export-strip"],"--time-lapse":[0,3,1,"cmdoption-osxphotos-export-time-lapse"],"--title":[0,3,1,"cmdoption-osxphotos-export-title"],"--to-date":[0,3,1,"cmdoption-osxphotos-export-to-date"],"--to-time":[0,3,1,"cmdoption-osxphotos-export-to-time"],"--touch-file":[0,3,1,"cmdoption-osxphotos-export-touch-file"],"--update":[0,3,1,"cmdoption-osxphotos-export-update"],"--use-photokit":[0,3,1,"cmdoption-osxphotos-export-use-photokit"],"--use-photos-export":[0,3,1,"cmdoption-osxphotos-export-use-photos-export"],"--uti":[0,3,1,"cmdoption-osxphotos-export-uti"],"--uuid":[0,3,1,"cmdoption-osxphotos-export-uuid"],"--uuid-from-file":[0,3,1,"cmdoption-osxphotos-export-uuid-from-file"],"--verbose":[0,3,1,"cmdoption-osxphotos-export-V"],"--xattr-template":[0,3,1,"cmdoption-osxphotos-export-xattr-template"],"-V":[0,3,1,"cmdoption-osxphotos-export-V"],"-i":[0,3,1,"cmdoption-osxphotos-export-i"],DEST:[0,3,1,"cmdoption-osxphotos-export-arg-DEST"],PHOTOS_LIBRARY:[0,3,1,"cmdoption-osxphotos-export-arg-PHOTOS_LIBRARY"]},"osxphotos-help":{TOPIC:[0,3,1,"cmdoption-osxphotos-help-arg-TOPIC"]},"osxphotos-info":{"--db":[0,3,1,"cmdoption-osxphotos-info-db"],"--json":[0,3,1,"cmdoption-osxphotos-info-json"],PHOTOS_LIBRARY:[0,3,1,"cmdoption-osxphotos-info-arg-PHOTOS_LIBRARY"]},"osxphotos-keywords":{"--db":[0,3,1,"cmdoption-osxphotos-keywords-db"],"--json":[0,3,1,"cmdoption-osxphotos-keywords-json"],PHOTOS_LIBRARY:[0,3,1,"cmdoption-osxphotos-keywords-arg-PHOTOS_LIBRARY"]},"osxphotos-labels":{"--db":[0,3,1,"cmdoption-osxphotos-labels-db"],"--json":[0,3,1,"cmdoption-osxphotos-labels-json"],PHOTOS_LIBRARY:[0,3,1,"cmdoption-osxphotos-labels-arg-PHOTOS_LIBRARY"]},"osxphotos-list":{"--json":[0,3,1,"cmdoption-osxphotos-list-json"]},"osxphotos-persons":{"--db":[0,3,1,"cmdoption-osxphotos-persons-db"],"--json":[0,3,1,"cmdoption-osxphotos-persons-json"],PHOTOS_LIBRARY:[0,3,1,"cmdoption-osxphotos-persons-arg-PHOTOS_LIBRARY"]},"osxphotos-places":{"--db":[0,3,1,"cmdoption-osxphotos-places-db"],"--json":[0,3,1,"cmdoption-osxphotos-places-json"],PHOTOS_LIBRARY:[0,3,1,"cmdoption-osxphotos-places-arg-PHOTOS_LIBRARY"]},"osxphotos-query":{"--add-to-album":[0,3,1,"cmdoption-osxphotos-query-add-to-album"],"--album":[0,3,1,"cmdoption-osxphotos-query-album"],"--burst":[0,3,1,"cmdoption-osxphotos-query-burst"],"--cloudasset":[0,3,1,"cmdoption-osxphotos-query-cloudasset"],"--db":[0,3,1,"cmdoption-osxphotos-query-db"],"--deleted":[0,3,1,"cmdoption-osxphotos-query-deleted"],"--deleted-only":[0,3,1,"cmdoption-osxphotos-query-deleted-only"],"--description":[0,3,1,"cmdoption-osxphotos-query-description"],"--edited":[0,3,1,"cmdoption-osxphotos-query-edited"],"--external-edit":[0,3,1,"cmdoption-osxphotos-query-external-edit"],"--favorite":[0,3,1,"cmdoption-osxphotos-query-favorite"],"--folder":[0,3,1,"cmdoption-osxphotos-query-folder"],"--from-date":[0,3,1,"cmdoption-osxphotos-query-from-date"],"--from-time":[0,3,1,"cmdoption-osxphotos-query-from-time"],"--has-comment":[0,3,1,"cmdoption-osxphotos-query-has-comment"],"--has-likes":[0,3,1,"cmdoption-osxphotos-query-has-likes"],"--has-raw":[0,3,1,"cmdoption-osxphotos-query-has-raw"],"--hdr":[0,3,1,"cmdoption-osxphotos-query-hdr"],"--hidden":[0,3,1,"cmdoption-osxphotos-query-hidden"],"--ignore-case":[0,3,1,"cmdoption-osxphotos-query-i"],"--in-album":[0,3,1,"cmdoption-osxphotos-query-in-album"],"--incloud":[0,3,1,"cmdoption-osxphotos-query-incloud"],"--is-reference":[0,3,1,"cmdoption-osxphotos-query-is-reference"],"--json":[0,3,1,"cmdoption-osxphotos-query-json"],"--keyword":[0,3,1,"cmdoption-osxphotos-query-keyword"],"--label":[0,3,1,"cmdoption-osxphotos-query-label"],"--live":[0,3,1,"cmdoption-osxphotos-query-live"],"--max-size":[0,3,1,"cmdoption-osxphotos-query-max-size"],"--min-size":[0,3,1,"cmdoption-osxphotos-query-min-size"],"--missing":[0,3,1,"cmdoption-osxphotos-query-missing"],"--name":[0,3,1,"cmdoption-osxphotos-query-name"],"--no-comment":[0,3,1,"cmdoption-osxphotos-query-no-comment"],"--no-description":[0,3,1,"cmdoption-osxphotos-query-no-description"],"--no-likes":[0,3,1,"cmdoption-osxphotos-query-no-likes"],"--no-place":[0,3,1,"cmdoption-osxphotos-query-no-place"],"--no-title":[0,3,1,"cmdoption-osxphotos-query-no-title"],"--not-burst":[0,3,1,"cmdoption-osxphotos-query-not-burst"],"--not-cloudasset":[0,3,1,"cmdoption-osxphotos-query-not-cloudasset"],"--not-favorite":[0,3,1,"cmdoption-osxphotos-query-not-favorite"],"--not-hdr":[0,3,1,"cmdoption-osxphotos-query-not-hdr"],"--not-hidden":[0,3,1,"cmdoption-osxphotos-query-not-hidden"],"--not-in-album":[0,3,1,"cmdoption-osxphotos-query-not-in-album"],"--not-incloud":[0,3,1,"cmdoption-osxphotos-query-not-incloud"],"--not-live":[0,3,1,"cmdoption-osxphotos-query-not-live"],"--not-missing":[0,3,1,"cmdoption-osxphotos-query-not-missing"],"--not-panorama":[0,3,1,"cmdoption-osxphotos-query-not-panorama"],"--not-portrait":[0,3,1,"cmdoption-osxphotos-query-not-portrait"],"--not-screenshot":[0,3,1,"cmdoption-osxphotos-query-not-screenshot"],"--not-selfie":[0,3,1,"cmdoption-osxphotos-query-not-selfie"],"--not-shared":[0,3,1,"cmdoption-osxphotos-query-not-shared"],"--not-slow-mo":[0,3,1,"cmdoption-osxphotos-query-not-slow-mo"],"--not-time-lapse":[0,3,1,"cmdoption-osxphotos-query-not-time-lapse"],"--only-movies":[0,3,1,"cmdoption-osxphotos-query-only-movies"],"--only-photos":[0,3,1,"cmdoption-osxphotos-query-only-photos"],"--panorama":[0,3,1,"cmdoption-osxphotos-query-panorama"],"--person":[0,3,1,"cmdoption-osxphotos-query-person"],"--place":[0,3,1,"cmdoption-osxphotos-query-place"],"--portrait":[0,3,1,"cmdoption-osxphotos-query-portrait"],"--query-eval":[0,3,1,"cmdoption-osxphotos-query-query-eval"],"--regex":[0,3,1,"cmdoption-osxphotos-query-regex"],"--screenshot":[0,3,1,"cmdoption-osxphotos-query-screenshot"],"--selfie":[0,3,1,"cmdoption-osxphotos-query-selfie"],"--shared":[0,3,1,"cmdoption-osxphotos-query-shared"],"--slow-mo":[0,3,1,"cmdoption-osxphotos-query-slow-mo"],"--time-lapse":[0,3,1,"cmdoption-osxphotos-query-time-lapse"],"--title":[0,3,1,"cmdoption-osxphotos-query-title"],"--to-date":[0,3,1,"cmdoption-osxphotos-query-to-date"],"--to-time":[0,3,1,"cmdoption-osxphotos-query-to-time"],"--uti":[0,3,1,"cmdoption-osxphotos-query-uti"],"--uuid":[0,3,1,"cmdoption-osxphotos-query-uuid"],"--uuid-from-file":[0,3,1,"cmdoption-osxphotos-query-uuid-from-file"],"-i":[0,3,1,"cmdoption-osxphotos-query-i"],PHOTOS_LIBRARY:[0,3,1,"cmdoption-osxphotos-query-arg-PHOTOS_LIBRARY"]},"osxphotos.PhotoInfo":{"export":[3,2,1,""],ExifInfo:[3,0,1,""],ExportResults:[3,0,1,""],ScoreInfo:[3,0,1,""],SearchInfo:[3,0,1,""],adjustments:[3,2,1,""],album_info:[3,2,1,""],albums:[3,2,1,""],asdict:[3,2,1,""],burst:[3,2,1,""],burst_album_info:[3,2,1,""],burst_albums:[3,2,1,""],burst_default_pick:[3,2,1,""],burst_key:[3,2,1,""],burst_photos:[3,2,1,""],burst_selected:[3,2,1,""],comments:[3,2,1,""],date:[3,2,1,""],date_added:[3,2,1,""],date_modified:[3,2,1,""],date_trashed:[3,2,1,""],description:[3,2,1,""],exif_info:[3,2,1,""],exiftool:[3,2,1,""],export2:[3,2,1,""],external_edit:[3,2,1,""],face_info:[3,2,1,""],favorite:[3,2,1,""],filename:[3,2,1,""],has_raw:[3,2,1,""],hasadjustments:[3,2,1,""],hdr:[3,2,1,""],height:[3,2,1,""],hidden:[3,2,1,""],import_info:[3,2,1,""],incloud:[3,2,1,""],intrash:[3,2,1,""],iscloudasset:[3,2,1,""],ismissing:[3,2,1,""],ismovie:[3,2,1,""],isphoto:[3,2,1,""],israw:[3,2,1,""],isreference:[3,2,1,""],json:[3,2,1,""],keywords:[3,2,1,""],labels:[3,2,1,""],labels_normalized:[3,2,1,""],likes:[3,2,1,""],live_photo:[3,2,1,""],location:[3,2,1,""],orientation:[3,2,1,""],original_filename:[3,2,1,""],original_filesize:[3,2,1,""],original_height:[3,2,1,""],original_orientation:[3,2,1,""],original_width:[3,2,1,""],panorama:[3,2,1,""],path:[3,2,1,""],path_derivatives:[3,2,1,""],path_edited:[3,2,1,""],path_live_photo:[3,2,1,""],path_raw:[3,2,1,""],person_info:[3,2,1,""],persons:[3,2,1,""],place:[3,2,1,""],portrait:[3,2,1,""],raw_original:[3,2,1,""],render_template:[3,2,1,""],score:[3,2,1,""],screenshot:[3,2,1,""],search_info:[3,2,1,""],search_info_normalized:[3,2,1,""],selfie:[3,2,1,""],shared:[3,2,1,""],slow_mo:[3,2,1,""],time_lapse:[3,2,1,""],title:[3,2,1,""],tzoffset:[3,2,1,""],uti:[3,2,1,""],uti_edited:[3,2,1,""],uti_original:[3,2,1,""],uti_raw:[3,2,1,""],uuid:[3,2,1,""],visible:[3,2,1,""],width:[3,2,1,""]},"osxphotos.PhotoInfo.ExifInfo":{aperture:[3,1,1,""],bit_rate:[3,1,1,""],camera_make:[3,1,1,""],camera_model:[3,1,1,""],codec:[3,1,1,""],duration:[3,1,1,""],exposure_bias:[3,1,1,""],flash_fired:[3,1,1,""],focal_length:[3,1,1,""],fps:[3,1,1,""],iso:[3,1,1,""],latitude:[3,1,1,""],lens_model:[3,1,1,""],longitude:[3,1,1,""],metering_mode:[3,1,1,""],sample_rate:[3,1,1,""],shutter_speed:[3,1,1,""],track_format:[3,1,1,""],white_balance:[3,1,1,""]},"osxphotos.PhotoInfo.ExportResults":{all_files:[3,2,1,""]},"osxphotos.PhotoInfo.ScoreInfo":{behavioral:[3,1,1,""],curation:[3,1,1,""],failure:[3,1,1,""],harmonious_color:[3,1,1,""],highlight_visibility:[3,1,1,""],immersiveness:[3,1,1,""],interaction:[3,1,1,""],interesting_subject:[3,1,1,""],intrusive_object_presence:[3,1,1,""],lively_color:[3,1,1,""],low_light:[3,1,1,""],noise:[3,1,1,""],overall:[3,1,1,""],pleasant_camera_tilt:[3,1,1,""],pleasant_composition:[3,1,1,""],pleasant_lighting:[3,1,1,""],pleasant_pattern:[3,1,1,""],pleasant_perspective:[3,1,1,""],pleasant_post_processing:[3,1,1,""],pleasant_reflection:[3,1,1,""],pleasant_symmetry:[3,1,1,""],promotion:[3,1,1,""],sharply_focused_subject:[3,1,1,""],tastefully_blurred:[3,1,1,""],well_chosen_subject:[3,1,1,""],well_framed_subject:[3,1,1,""],well_timed_shot:[3,1,1,""]},"osxphotos.PhotoInfo.SearchInfo":{activities:[3,2,1,""],all:[3,2,1,""],asdict:[3,2,1,""],bodies_of_water:[3,2,1,""],city:[3,2,1,""],country:[3,2,1,""],holidays:[3,2,1,""],labels:[3,2,1,""],locality_names:[3,2,1,""],media_types:[3,2,1,""],month:[3,2,1,""],neighborhoods:[3,2,1,""],place_names:[3,2,1,""],season:[3,2,1,""],state:[3,2,1,""],state_abbreviation:[3,2,1,""],streets:[3,2,1,""],venue_types:[3,2,1,""],venues:[3,2,1,""],year:[3,2,1,""]},"osxphotos.PhotosDB":{album_info:[3,2,1,""],album_info_shared:[3,2,1,""],albums:[3,2,1,""],albums_as_dict:[3,2,1,""],albums_shared:[3,2,1,""],albums_shared_as_dict:[3,2,1,""],db_path:[3,2,1,""],db_version:[3,2,1,""],folder_info:[3,2,1,""],folders:[3,2,1,""],get_db_connection:[3,2,1,""],get_photo:[3,2,1,""],import_info:[3,2,1,""],keywords:[3,2,1,""],keywords_as_dict:[3,2,1,""],labels:[3,2,1,""],labels_as_dict:[3,2,1,""],labels_normalized:[3,2,1,""],labels_normalized_as_dict:[3,2,1,""],library_path:[3,2,1,""],person_info:[3,2,1,""],persons:[3,2,1,""],persons_as_dict:[3,2,1,""],photos:[3,2,1,""],photos_by_uuid:[3,2,1,""],query:[3,2,1,""]},osxphotos:{"--db":[0,3,1,"cmdoption-osxphotos-db"],"--json":[0,3,1,"cmdoption-osxphotos-json"],"--version":[0,3,1,"cmdoption-osxphotos-v"],"-v":[0,3,1,"cmdoption-osxphotos-v"],PhotoInfo:[3,0,1,""],PhotosDB:[3,0,1,""]}},objnames:{"0":["py","class","Python class"],"1":["py","attribute","Python attribute"],"2":["py","method","Python method"],"3":["std","cmdoption","program option"]},objtypes:{"0":"py:class","1":"py:attribute","2":"py:method","3":"std:cmdoption"},terms:{"048576mb":0,"100":4,"1048576":0,"120":3,"12t12":0,"1gb":1,"2000":0,"2001":0,"2015":4,"2019":0,"2020":4,"2021":4,"8601":0,"boolean":[3,4],"byte":[0,3],"case":[0,3,4],"class":[0,3],"default":[0,3,4],"export":3,"final":4,"float":3,"function":3,"import":[0,1,3,4],"int":3,"long":3,"new":[0,1,3,4],"null":4,"public":3,"return":[0,3],"short":4,"true":[1,3,4],"try":4,"while":4,AND:[0,3],And:4,For:[0,3,4],GPS:4,NAS:4,One:4,That:4,The:[0,1,3,4],Then:[1,4],There:4,These:[0,4],Use:[0,3,4],Using:4,With:4,__main__:1,__name__:1,_bearbeiten:0,_edit:[0,4],_origin:0,abbrevi:3,abil:1,abl:[1,4],about:[1,3,4],abov:[1,4],absolut:3,access:[3,4],accord:1,activ:3,actual:[0,3],add:[0,4],added:[0,3,4],adding:0,addit:[0,3,4],adjust:[3,4],adjustmentsinfo:3,adob:0,advanc:[1,4],advantag:0,after:4,again:4,against:3,ahead:3,aka:1,album:[3,4],album_info:3,album_info_shar:3,album_nam:1,albuminfo:3,albums_as_dict:[1,3],albums_shar:3,albums_shared_as_dict:3,algorithm:4,alic:1,all:[0,3],all_fil:3,allow:[0,1,4],almost:4,alpha:0,alreadi:[0,3,4],alreai:3,also:[0,1,3,4],altern:[0,1,4],alwai:4,amount:4,ani:[0,1,3,4],anoth:4,anyth:4,apertur:3,apf:0,app:[0,1,3,4],append:[0,4],appl:[0,1,3,4],applescript:[0,3,4],appli:[0,3,4],applic:[1,4],april:4,aren:[1,3],arg:[0,1,3],argument:[0,1],asdict:3,asset:[3,4],assign:4,associ:[0,1,3,4],associt:3,assum:3,asynchroun:3,attach:4,attempt:[0,1,3,4],attribut:[0,3],author:[0,4],auto:1,automat:[0,4],avail:4,awar:3,back:4,backup:4,bar:1,base:3,basi:4,basic:4,beach:0,becaus:[1,4],been:[0,1,3,4],befor:[0,1,4],begin:0,behavior:[0,3],being:[0,4],below:[0,1],best:[0,3,4],beta:1,better:4,between:[3,4],big:[],bit:3,bit_rat:3,blank:4,bodi:3,bodies_of_wat:3,bool:3,both:[0,1,4],brace:4,brew:1,buggi:4,built:1,burst:[0,3],burst_album:3,burst_album_info:3,burst_default_pick:3,burst_kei:3,burst_photo:3,burst_select:3,call:[1,3,4],callabl:3,camera:[0,3],camera_mak:3,camera_model:3,can:[0,1,4],cannot:[0,3],canon:3,capabl:4,caption:4,care:1,carri:4,catalina:1,categor:3,caus:4,caution:0,certain:0,certainli:4,chang:[0,3,4],charact:[1,3,4],chose:4,citi:3,classic:4,classif:[0,1,4],classifi:4,cleanup:0,cli:1,click:1,clone:[0,1],cloud:[3,4],cloudasset:0,code:1,codec:3,collis:0,com:[1,3],combin:4,comfort:1,comma:4,command:4,command_nam:1,comment:[0,3,4],common:4,commun:4,compar:3,compat:[0,4],complex:[0,4],compon:0,comprehens:[0,4],compress:[0,3],comput:3,concept:4,condit:4,config:[0,4],configur:4,conform:3,conjunct:[0,4],connect:[0,3,4],consist:0,contain:[0,1,3,4],context:0,contribut:4,convert:[0,3],convert_to_jpeg:3,converted_to_jpeg:3,coordin:4,copi:[0,3,4],copyright:[0,4],correct:[3,4],correspond:0,could:[0,1,4],count:3,countri:[3,4],cours:4,cover:4,cr2:3,crash:4,creat:0,created:0,creation:[3,4],creationd:0,criteria:[0,3,4],crop:4,csv:[0,4],curat:3,curli:4,current:[0,3],cursor:3,custom:4,cut:4,dai:[0,4],data:[0,3,4],databas:[0,1,3,4],date:[0,3],date_ad:3,date_modifi:3,date_trash:3,datecr:0,datetim:3,datetimeorigin:[0,3],daunt:4,db_path:3,db_version:3,dbfile:3,decid:4,def:1,default_album:1,degre:3,delet:[0,3],deleted_directori:3,deleted_fil:3,demonstr:4,deriv:3,desc:0,descend:3,descr:[0,4],describ:[1,4],descript:[0,1,3,4],description_templ:3,design:4,desir:4,desktop:4,dest:[0,3],dest_dir:1,destin:[1,3],detail:[0,3,4],determin:[3,4],devic:4,dict:3,did:[0,4],differ:[0,1,3,4],digikam:[0,4],digit:4,dir:1,direct:0,directli:[0,1,4],directori:[0,3],dirnam:3,disabl:1,discuss:4,disk:[0,3],distinguish:4,dng:4,doc:0,document:0,doe:[0,1,3,4],doesn:[0,4],don:[0,4],done:4,dot:4,down:4,download:[0,1,3,4],drive:0,drop:[0,3,4],dry:[0,3],dry_run:3,dsc05678:4,dump:1,duplic:1,durat:3,dure:[0,3],dynam:0,each:[0,3,4],earlier:3,easi:4,easiest:1,easili:[1,4],echo:1,edit:[0,1,3],edited_nam:1,edited_vers:3,editor:[0,3],either:[0,1,3,4],els:[1,3,4],embed:[0,4],empti:[3,4],enabl:4,encount:4,end:0,ensur:[3,4],entir:[3,4],environ:1,equival:0,error:[0,3,4],error_str:3,etc:[0,1,3,4],eval:0,evalu:[0,3,4],even:[0,3,4],everi:4,everyth:4,exact:3,exactli:4,exampl:[0,3],excel:4,except:3,execut:1,exif:[0,3],exif_info:3,exif_upd:3,exifinfo:3,exiftool:[0,1,3,4],exiftool_error:3,exiftool_flag:3,exiftool_path:0,exiftool_pod:0,exiftool_warn:3,exist:[0,1,3],exit:[0,1],expand:[3,4],expand_inplac:3,expandus:1,expect:4,experi:0,experiment:[0,4],explain:4,explan:4,explicitli:4,explor:4,export2:3,export_as_hardlink:3,export_db:3,export_path:1,exportdb:0,exportdb_abc:3,exportdb_fil:0,exportdbinmemori:3,exported_album:3,exportresult:3,exposure_bia:3,express:0,ext:[0,4],extend:[0,3,4],extens:[0,3,4],extern:[0,3],external_edit:3,extract:3,face:[0,1,3,4],face_info:3,faceinfo:3,fail:3,failur:3,fals:[3,4],familiar:1,faster:[0,3],favorit:[0,3,4],featur:[0,4],few:4,fie:[],field:[3,4],file:[0,3],filenam:[0,1,3],filename_origin:0,filepath:1,files:3,filesystem:4,fileutil:3,fileutilabc:3,fileutilnoop:3,filter:[0,4],find:[0,4],finder:0,findercom:[0,4],first:[0,3,4],flag:[0,3],flash_fir:3,flexibl:[1,4],focal_length:3,focu:4,folder1:4,folder2:4,folder:[0,3,4],folder_album:[0,3,4],folder_info:3,folderinfo:3,follow:[0,1,4],foo:1,forc:[0,3,4],form:[0,4],format:[0,1,3,4],found:[0,1,3,4],fps:3,free:4,from:[0,3],from_dat:[0,3],from_tim:0,front:[0,3],full:[0,3,4],futur:4,gener:[3,4],geoloc:[0,3,4],get:[1,3,4],get_db_connect:3,get_photo:3,github:[0,1],give:0,good:4,gopro:4,gpscoordin:0,gpslatitud:0,gpslatituderef:0,gpslongitud:0,gpslongituderef:0,gpsposit:0,gpu:0,group:[0,3],had:[0,4],handl:[0,4],happen:0,hard:4,hardlink:[0,3],harmonious_color:3,has:[0,1,3,4],has_raw:3,hasadjust:[1,3],hasn:4,have:[0,3,4],haven:0,hdr:[0,3],headlin:[0,4],heic:0,height:3,help:[1,4],here:[0,4],hidden:[0,3,4],hierarch:4,high:0,highli:0,highlight_vis:3,hold:[3,4],holidai:3,homebrew:1,hopefulli:4,how:[0,4],howev:4,html:0,http:[0,1,3,4],icloud:[0,3,4],identifi:[0,3,4],ignor:[0,3,4],ignore_date_modifi:3,ignore_signatur:3,imag:[0,1,3],imagedescript:0,img_1234:[0,3,4],img_1234_2021:4,img_1234_edit:4,immedi:[3,4],immers:3,implement:[0,4],import_info:3,importinfo:3,incloud:[0,3],includ:[0,1,3,4],incorrect:3,increment:3,index:1,individu:3,info:[1,3],inform:[0,1,3,4],initi:3,inplace_sep:3,insensit:0,insert:4,insid:4,instal:[0,3,4],instanc:[3,4],instead:[0,3,4],instruct:[1,4],integ:0,integr:4,intend:0,intent:4,interact:[0,1,3],interest:4,interesting_subject:3,interfac:4,intermitt:0,intern:4,internet:0,interpret:4,intrash:3,intrusive_object_pres:3,invalid:[1,3],invest:4,involv:4,iphon:4,iptc:[0,3],is_valid_filepath:1,iscloudasset:3,isdir:1,ismiss:[1,3],ismovi:3,isn:4,iso:[0,3],isphoto:3,israw:3,isrefer:3,item:[0,3],iterm2:0,its:4,itself:4,john:1,join:[1,3,4],jpeg:[0,3],jpeg_ext:3,jpeg_qual:[0,3],jpg:[0,3,4],json:[0,3,4],just:[1,4],keep:4,kei:[3,4],keyword:[3,4],keyword_templ:3,keywords_as_dict:[1,3],kind:4,know:[0,3],known:4,label:[3,4],labels_as_dict:3,labels_norm:3,labels_normalized_as_dict:3,laps:[0,3],larg:[0,1,4],largest:3,last:[0,1,4],later:0,latest:1,latitud:3,lead:[0,3,4],learn:[3,4],left:4,lens_model:3,less:4,level:[0,3],librari:[0,3],library_path:[1,3],licens:[0,1],lightroom:[0,4],liglob:3,like:[0,1,3,4],limit:4,line:4,list:[1,3,4],liter:4,littl:4,live:[0,3,4],live_photo:3,lively_color:3,load:0,local:[3,4],locality_nam:3,locat:[3,4],log:3,logic:4,longitud:3,look:[0,1,3,4],lookup:4,lot:0,low_light:3,mac:[0,1,4],machin:[1,3,4],maco:[0,1],mai:[0,3,4],main:1,maintain:4,make:[1,4],makedir:1,manag:4,mangl:3,mani:[0,4],manner:4,manual:4,mark:[0,3,4],match:[0,3,4],matter:4,max:0,maximum:[0,3],mean:3,media:[3,4],media_typ:[1,3],memori:3,mention:4,mere:4,merg:[0,3,4],merge_exif_keyword:3,merge_exif_person:3,messag:1,metadata:[0,3],metering_mod:3,method:3,mib:0,microsoft:[0,4],might:[3,4],min:[0,1],minor:0,minut:4,miss:[0,1,3],missing_album:3,mode:[0,3,4],modif:[0,3,4],modifi:[0,4],modifyd:[0,3],modul:1,month:[3,4],more:[0,1,3,4],most:[0,4],motion:[0,3],mov:[0,3],move:4,movi:[0,1,3,4],multi:[0,3,4],multipl:[0,1],must:[0,3,4],myalternatelibrari:4,mysteri:3,naiv:3,name:[0,3,4],nativ:4,ndescript:4,necessari:[],need:[1,4],neighborhood:3,neither:[0,1],nest:4,network:[0,4],never:4,nevertheless:4,newlin:4,next:4,nist:0,nois:3,nolabel:4,non:[0,3,4],none:[1,3],none_str:3,normal:[0,3,4],note:[0,1,3,4],noth:4,notic:3,now:[1,4],number:4,object:[0,3],obvious:0,occasion:4,occur:[0,3],offer:4,offset:3,offsettimeorigin:0,often:4,older:3,one:[0,3,4],onli:[0,3],open:[0,1,4],oper:4,oppos:4,optim:4,option:[0,1,3],order:[0,1,3],org:[0,3,4],organ:[0,4],orient:3,origin:[0,1,3,4],original_filenam:[1,3],original_files:3,original_height:3,original_nam:[0,4],original_orient:3,original_width:3,osxphoto:[],osxphotos_export:[0,4],other:[0,1,3,4],otherwis:[1,3,4],out:[0,1,4],output:[0,3,4],outsid:3,over:4,overal:3,overrid:0,overwrit:[0,3],own:[0,1],page:1,pair:[0,3,4],panorama:[0,3],paramet:[1,3],part:[0,3,4],particular:3,pass:[0,3],path:[0,1,3,4],path_deriv:3,path_edit:[1,3],path_live_photo:3,path_raw:3,path_sep:3,pathlib:1,pathvalid:1,per:0,perform:4,period:4,permit:0,person:[3,4],person_info:3,personinfo:3,personinimag:0,persons_as_dict:[1,3],philipp:4,philosophi:4,photo:[0,3],photo_ext:0,photo_filenam:0,photoinfo:[0,3],photokit:[0,4],photonam:[0,4],photoname_bearbeiten:0,photoname_edit:0,photopr:4,photos_by_uuid:3,photos_librari:[0,1],photosdb:[1,3],photoslibrari:[0,1,4],phototempl:3,pick:4,pictur:[0,1,3,4],pixel:3,place:[1,3,4],place_nam:3,placeinfo:3,plain:4,platform:1,pleasant_camera_tilt:3,pleasant_composit:3,pleasant_light:3,pleasant_pattern:3,pleasant_perspect:3,pleasant_post_process:3,pleasant_reflect:3,pleasant_symmetri:3,png:0,popul:4,portrait:[0,3,4],posit:[0,1],possibl:4,power:4,pre:1,preced:[0,4],prefer:4,present:0,preserv:4,preview:[0,3,4],previous:[0,4],primari:0,print:[0,3,4],pro:4,probabl:4,process:[0,3,4],produc:[3,4],program:4,project:1,promot:3,properti:3,provid:[0,1,3,4],purpos:4,put:4,pylint:1,pypi:1,python3:1,python:[0,1],qualiti:[0,3,4],queri:[1,3,4],queryopt:3,quickli:4,quicktim:[0,3],quik:4,quit:4,rais:3,rang:[0,3,4],rather:4,raw:[0,3,4],raw_origin:3,raw_photo:3,read:[1,4],readabl:[3,4],reason:4,recent:[0,3],recommend:1,refer:[0,1,3,4],referenc:0,regardless:3,regex:0,region:0,regular:[0,4],regularli:4,relat:3,releas:1,reliabl:0,remov:[0,4],renam:[0,4],render:[0,3,4],render_templ:3,rendered_str:3,repeat:[0,4],repeatedli:4,replac:[0,3,4],replace_keyword:3,repo:1,report:0,repres:[0,3],represent:3,requir:[0,1,3],restart:4,result:[0,3,4],resum:4,retri:[0,4],retriev:4,reus:0,revers:[0,3,4],rhettbul:[0,1],rich:4,rise:4,roll:4,run:[0,1,3],same:[0,3,4],sample_r:3,sanit:3,sanitize_filepath:1,save:0,score:3,scoreinfo:3,screenshot:[0,3,4],script:0,search:[0,1,3,4],search_info:3,search_info_norm:3,searchinfo:3,season:3,second:3,section:4,see:[0,1,3,4],seem:4,seen:4,select:[3,4],self:3,selfi:[0,3],sep:3,separ:[3,4],session:3,set:[0,3,4],setup:1,setuptool:1,sever:4,share:[0,3,4],sharply_focused_subject:3,should:[1,4],show:[0,1,3,4],shutter_spe:3,sidecar:[0,3],sidecar_drop_ext:3,sidecar_exiftool:3,sidecar_exiftool_skip:3,sidecar_exiftool_written:3,sidecar_ext:[0,4],sidecar_json:3,sidecar_json_skip:3,sidecar_json_written:3,sidecar_xmp:3,sidecar_xmp_skip:3,sidecar_xmp_written:3,sierra:1,signatur:[0,3,4],silent:3,simpl:1,simpli:[1,4],sinc:4,singl:[0,3,4],size:[0,1,3,4],skip:[0,1,3,4],skipped_album:3,slow:[0,3],slow_mo:3,smart:4,smith:1,some:[3,4],someth:[3,4],somewhat:4,sort:3,sourc:3,space:[0,3],specif:[1,3,4],specifi:[0,3],spend:4,spot:4,spotlight:[0,1,4],sqlite3:3,stack:3,standard:4,start:[0,1,4],state:[0,3,4],state_abbrevi:3,statement:4,statu:3,still:[3,4],storag:4,storat:3,store:[0,3,4],str:3,street:3,string:[0,3,4],strip:[0,3,4],subfold:0,subject:0,subsequ:0,subset:4,substitut:3,suffix:[0,3,4],summer:4,suppli:3,sur:1,sure:[0,1,4],synch:[0,3,4],syntax:4,sys:1,system:[0,4],tabl:3,tag:[0,1,3,4],tagnam:[0,4],tagslist:0,take:[1,4],taken:[0,4],tastefully_blur:3,tell:4,templat:[0,1,3,4],template_str:3,term:[3,4],termin:[0,1,4],test:[0,1,4],text:[3,4],than:[0,3,4],thank:4,thei:[0,3,4],them:[0,3,4],thi:[0,1,3,4],thing:4,think:4,those:4,thousand:4,through:4,throughout:4,thu:[1,4],time:[0,3,4],time_laps:3,timecr:0,timeout:3,timezon:[0,3],titl:[0,1,3,4],titlen:4,to_dat:[0,3],to_tim:0,todai:0,togeth:4,toml:[0,4],tool:4,top:[0,3],topic:0,touch:[0,3,4],touch_fil:3,town:3,track:4,track_format:3,trail:[0,3,4],trash:3,travel:4,treat:[0,3,4],tremend:4,tupl:3,tutori:4,two:0,type:[0,3,4],tzoffset:3,underscor:4,understand:4,unedit:[1,4],unfil:1,uniform:[0,3],unit:0,unless:3,unlik:[0,4],unmatch:3,unreli:4,until:[1,3],updat:[0,3],upon:[0,3,4],use:[0,3,4],use_albums_as_keyword:3,use_persons_as_keyword:3,use_photokit:3,use_photos_export:3,useabl:0,used:[0,1,3,4],useful:[0,4],user:3,userdata:0,uses:[0,3,4],using:[0,3,4],usual:4,utc:3,uti:[0,3],uti_edit:3,uti_origin:3,uti_raw:3,util:[1,3],uuid:[0,1,3],vacat:[3,4],valid:[0,1,3,4],valu:[0,1,3,4],varieti:4,variou:3,venu:3,venue_typ:3,verbos:[0,3,4],veri:[1,4],verifi:[1,4],versa:1,version:[0,1,3,4],via:[0,1,3,4],vice:1,video:[0,3,4],virtual:1,visbl:3,visibl:3,volum:[0,4],wai:[1,4],want:[0,1,3,4],warn:[0,1,3],water:3,websit:4,wed:3,well:[0,1,3,4],well_chosen_subject:3,well_framed_subject:3,well_timed_shot:3,went:0,were:[0,3,4],what:[0,3,4],when:[0,3,4],where:[0,4],whether:0,which:[0,1,3,4],white:3,white_bal:3,whitespac:[0,4],whose:0,wide:4,width:3,without:[0,4],won:4,work:[0,1,3,4],workflow:4,worst:4,worth:4,would:[0,4],write:[0,3,4],written:[0,3,4],xattr:[0,4],xattr_skip:3,xattr_written:3,xmp:[0,3,4],year:[0,3,4],yet:[3,4],you:[0,1,3,4],your:[0,1]},titles:["osxphotos command line interface (CLI)","Welcome to osxphotos\u2019s documentation!","osxphotos","osxphotos package","Export your photos"],titleterms:{"200mb":1,"default":1,"export":[0,1,4],about:0,actual:4,add:1,album:[0,1],all:[1,4],attribut:4,awail:1,base:1,big:1,certain:4,chang:1,cli:0,command:[0,1],conclus:4,convert:4,countri:1,creat:[1,4],creation:1,date:[1,4],desktop:1,digit:1,directori:[1,4],disk:4,document:1,dry:4,dump:0,edit:4,exampl:[1,4],exif:1,extern:4,file:[1,4],filenam:4,find:1,finder:4,folder:1,from:[1,4],full:1,git:1,group:1,help:0,higher:1,imag:4,indic:1,info:0,instal:1,interfac:[0,1],jpeg:4,json:1,keyword:[0,1],kid:1,label:[0,1],larger:1,librari:[1,4],line:[0,1],list:0,load:4,media:1,metadata:[1,4],miss:4,modul:3,month:1,name:1,necessari:1,nocountri:1,onli:[1,4],oper:1,option:4,osxphoto:[0,1,2,3,4],ouput:1,output:1,packag:[1,3],person:[0,1],photo:[1,4],pip:1,pipx:1,place:0,previou:4,print:1,queri:0,report:4,repositori:1,result:1,run:4,save:4,sidecar:4,specifi:[1,4],structur:[1,4],support:1,system:1,tabl:1,than:1,them:1,type:1,updat:[1,4],usag:1,use:1,user:4,uses:1,using:1,verbos:1,video:1,welcom:1,what:1,when:1,write:1,year:1,your:4}}) \ No newline at end of file +Search.setIndex({docnames:["cli","index","modules","reference"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.intersphinx":1,"sphinx.ext.viewcode":1,sphinx:56},filenames:["cli.rst","index.rst","modules.rst","reference.rst"],objects:{"osxphotos-albums":{"--db":[0,4,1,"cmdoption-osxphotos-albums-db"],"--json":[0,4,1,"cmdoption-osxphotos-albums-json"],PHOTOS_LIBRARY:[0,4,1,"cmdoption-osxphotos-albums-arg-PHOTOS_LIBRARY"]},"osxphotos-dump":{"--db":[0,4,1,"cmdoption-osxphotos-dump-db"],"--deleted":[0,4,1,"cmdoption-osxphotos-dump-deleted"],"--deleted-only":[0,4,1,"cmdoption-osxphotos-dump-deleted-only"],"--json":[0,4,1,"cmdoption-osxphotos-dump-json"],PHOTOS_LIBRARY:[0,4,1,"cmdoption-osxphotos-dump-arg-PHOTOS_LIBRARY"]},"osxphotos-export":{"--add-exported-to-album":[0,4,1,"cmdoption-osxphotos-export-add-exported-to-album"],"--add-missing-to-album":[0,4,1,"cmdoption-osxphotos-export-add-missing-to-album"],"--add-skipped-to-album":[0,4,1,"cmdoption-osxphotos-export-add-skipped-to-album"],"--album":[0,4,1,"cmdoption-osxphotos-export-album"],"--album-keyword":[0,4,1,"cmdoption-osxphotos-export-album-keyword"],"--burst":[0,4,1,"cmdoption-osxphotos-export-burst"],"--cleanup":[0,4,1,"cmdoption-osxphotos-export-cleanup"],"--convert-to-jpeg":[0,4,1,"cmdoption-osxphotos-export-convert-to-jpeg"],"--current-name":[0,4,1,"cmdoption-osxphotos-export-current-name"],"--db":[0,4,1,"cmdoption-osxphotos-export-db"],"--deleted":[0,4,1,"cmdoption-osxphotos-export-deleted"],"--deleted-only":[0,4,1,"cmdoption-osxphotos-export-deleted-only"],"--description":[0,4,1,"cmdoption-osxphotos-export-description"],"--description-template":[0,4,1,"cmdoption-osxphotos-export-description-template"],"--directory":[0,4,1,"cmdoption-osxphotos-export-directory"],"--download-missing":[0,4,1,"cmdoption-osxphotos-export-download-missing"],"--dry-run":[0,4,1,"cmdoption-osxphotos-export-dry-run"],"--duplicate":[0,4,1,"cmdoption-osxphotos-export-duplicate"],"--edited":[0,4,1,"cmdoption-osxphotos-export-edited"],"--edited-suffix":[0,4,1,"cmdoption-osxphotos-export-edited-suffix"],"--exiftool":[0,4,1,"cmdoption-osxphotos-export-exiftool"],"--exiftool-merge-keywords":[0,4,1,"cmdoption-osxphotos-export-exiftool-merge-keywords"],"--exiftool-merge-persons":[0,4,1,"cmdoption-osxphotos-export-exiftool-merge-persons"],"--exiftool-option":[0,4,1,"cmdoption-osxphotos-export-exiftool-option"],"--exiftool-path":[0,4,1,"cmdoption-osxphotos-export-exiftool-path"],"--export-as-hardlink":[0,4,1,"cmdoption-osxphotos-export-export-as-hardlink"],"--export-by-date":[0,4,1,"cmdoption-osxphotos-export-export-by-date"],"--exportdb":[0,4,1,"cmdoption-osxphotos-export-exportdb"],"--external-edit":[0,4,1,"cmdoption-osxphotos-export-external-edit"],"--favorite":[0,4,1,"cmdoption-osxphotos-export-favorite"],"--filename":[0,4,1,"cmdoption-osxphotos-export-filename"],"--finder-tag-keywords":[0,4,1,"cmdoption-osxphotos-export-finder-tag-keywords"],"--finder-tag-template":[0,4,1,"cmdoption-osxphotos-export-finder-tag-template"],"--folder":[0,4,1,"cmdoption-osxphotos-export-folder"],"--from-date":[0,4,1,"cmdoption-osxphotos-export-from-date"],"--from-time":[0,4,1,"cmdoption-osxphotos-export-from-time"],"--has-comment":[0,4,1,"cmdoption-osxphotos-export-has-comment"],"--has-likes":[0,4,1,"cmdoption-osxphotos-export-has-likes"],"--has-raw":[0,4,1,"cmdoption-osxphotos-export-has-raw"],"--hdr":[0,4,1,"cmdoption-osxphotos-export-hdr"],"--hidden":[0,4,1,"cmdoption-osxphotos-export-hidden"],"--ignore-case":[0,4,1,"cmdoption-osxphotos-export-i"],"--ignore-date-modified":[0,4,1,"cmdoption-osxphotos-export-ignore-date-modified"],"--ignore-signature":[0,4,1,"cmdoption-osxphotos-export-ignore-signature"],"--in-album":[0,4,1,"cmdoption-osxphotos-export-in-album"],"--is-reference":[0,4,1,"cmdoption-osxphotos-export-is-reference"],"--jpeg-ext":[0,4,1,"cmdoption-osxphotos-export-jpeg-ext"],"--jpeg-quality":[0,4,1,"cmdoption-osxphotos-export-jpeg-quality"],"--keyword":[0,4,1,"cmdoption-osxphotos-export-keyword"],"--keyword-template":[0,4,1,"cmdoption-osxphotos-export-keyword-template"],"--label":[0,4,1,"cmdoption-osxphotos-export-label"],"--live":[0,4,1,"cmdoption-osxphotos-export-live"],"--load-config":[0,4,1,"cmdoption-osxphotos-export-load-config"],"--location":[0,4,1,"cmdoption-osxphotos-export-location"],"--max-size":[0,4,1,"cmdoption-osxphotos-export-max-size"],"--min-size":[0,4,1,"cmdoption-osxphotos-export-min-size"],"--missing":[0,4,1,"cmdoption-osxphotos-export-missing"],"--name":[0,4,1,"cmdoption-osxphotos-export-name"],"--no-comment":[0,4,1,"cmdoption-osxphotos-export-no-comment"],"--no-description":[0,4,1,"cmdoption-osxphotos-export-no-description"],"--no-likes":[0,4,1,"cmdoption-osxphotos-export-no-likes"],"--no-location":[0,4,1,"cmdoption-osxphotos-export-no-location"],"--no-place":[0,4,1,"cmdoption-osxphotos-export-no-place"],"--no-title":[0,4,1,"cmdoption-osxphotos-export-no-title"],"--not-burst":[0,4,1,"cmdoption-osxphotos-export-not-burst"],"--not-favorite":[0,4,1,"cmdoption-osxphotos-export-not-favorite"],"--not-hdr":[0,4,1,"cmdoption-osxphotos-export-not-hdr"],"--not-hidden":[0,4,1,"cmdoption-osxphotos-export-not-hidden"],"--not-in-album":[0,4,1,"cmdoption-osxphotos-export-not-in-album"],"--not-live":[0,4,1,"cmdoption-osxphotos-export-not-live"],"--not-panorama":[0,4,1,"cmdoption-osxphotos-export-not-panorama"],"--not-portrait":[0,4,1,"cmdoption-osxphotos-export-not-portrait"],"--not-screenshot":[0,4,1,"cmdoption-osxphotos-export-not-screenshot"],"--not-selfie":[0,4,1,"cmdoption-osxphotos-export-not-selfie"],"--not-shared":[0,4,1,"cmdoption-osxphotos-export-not-shared"],"--not-slow-mo":[0,4,1,"cmdoption-osxphotos-export-not-slow-mo"],"--not-time-lapse":[0,4,1,"cmdoption-osxphotos-export-not-time-lapse"],"--only-movies":[0,4,1,"cmdoption-osxphotos-export-only-movies"],"--only-new":[0,4,1,"cmdoption-osxphotos-export-only-new"],"--only-photos":[0,4,1,"cmdoption-osxphotos-export-only-photos"],"--original-suffix":[0,4,1,"cmdoption-osxphotos-export-original-suffix"],"--overwrite":[0,4,1,"cmdoption-osxphotos-export-overwrite"],"--panorama":[0,4,1,"cmdoption-osxphotos-export-panorama"],"--person":[0,4,1,"cmdoption-osxphotos-export-person"],"--person-keyword":[0,4,1,"cmdoption-osxphotos-export-person-keyword"],"--place":[0,4,1,"cmdoption-osxphotos-export-place"],"--portrait":[0,4,1,"cmdoption-osxphotos-export-portrait"],"--post-command":[0,4,1,"cmdoption-osxphotos-export-post-command"],"--post-function":[0,4,1,"cmdoption-osxphotos-export-post-function"],"--preview":[0,4,1,"cmdoption-osxphotos-export-preview"],"--preview-if-missing":[0,4,1,"cmdoption-osxphotos-export-preview-if-missing"],"--preview-suffix":[0,4,1,"cmdoption-osxphotos-export-preview-suffix"],"--query-eval":[0,4,1,"cmdoption-osxphotos-export-query-eval"],"--query-function":[0,4,1,"cmdoption-osxphotos-export-query-function"],"--regex":[0,4,1,"cmdoption-osxphotos-export-regex"],"--replace-keywords":[0,4,1,"cmdoption-osxphotos-export-replace-keywords"],"--report":[0,4,1,"cmdoption-osxphotos-export-report"],"--retry":[0,4,1,"cmdoption-osxphotos-export-retry"],"--save-config":[0,4,1,"cmdoption-osxphotos-export-save-config"],"--screenshot":[0,4,1,"cmdoption-osxphotos-export-screenshot"],"--selected":[0,4,1,"cmdoption-osxphotos-export-selected"],"--selfie":[0,4,1,"cmdoption-osxphotos-export-selfie"],"--shared":[0,4,1,"cmdoption-osxphotos-export-shared"],"--sidecar":[0,4,1,"cmdoption-osxphotos-export-sidecar"],"--sidecar-drop-ext":[0,4,1,"cmdoption-osxphotos-export-sidecar-drop-ext"],"--skip-bursts":[0,4,1,"cmdoption-osxphotos-export-skip-bursts"],"--skip-edited":[0,4,1,"cmdoption-osxphotos-export-skip-edited"],"--skip-live":[0,4,1,"cmdoption-osxphotos-export-skip-live"],"--skip-original-if-edited":[0,4,1,"cmdoption-osxphotos-export-skip-original-if-edited"],"--skip-raw":[0,4,1,"cmdoption-osxphotos-export-skip-raw"],"--slow-mo":[0,4,1,"cmdoption-osxphotos-export-slow-mo"],"--strip":[0,4,1,"cmdoption-osxphotos-export-strip"],"--time-lapse":[0,4,1,"cmdoption-osxphotos-export-time-lapse"],"--title":[0,4,1,"cmdoption-osxphotos-export-title"],"--to-date":[0,4,1,"cmdoption-osxphotos-export-to-date"],"--to-time":[0,4,1,"cmdoption-osxphotos-export-to-time"],"--touch-file":[0,4,1,"cmdoption-osxphotos-export-touch-file"],"--update":[0,4,1,"cmdoption-osxphotos-export-update"],"--use-photokit":[0,4,1,"cmdoption-osxphotos-export-use-photokit"],"--use-photos-export":[0,4,1,"cmdoption-osxphotos-export-use-photos-export"],"--uti":[0,4,1,"cmdoption-osxphotos-export-uti"],"--uuid":[0,4,1,"cmdoption-osxphotos-export-uuid"],"--uuid-from-file":[0,4,1,"cmdoption-osxphotos-export-uuid-from-file"],"--verbose":[0,4,1,"cmdoption-osxphotos-export-V"],"--xattr-template":[0,4,1,"cmdoption-osxphotos-export-xattr-template"],"-V":[0,4,1,"cmdoption-osxphotos-export-V"],"-i":[0,4,1,"cmdoption-osxphotos-export-i"],DEST:[0,4,1,"cmdoption-osxphotos-export-arg-DEST"],PHOTOS_LIBRARY:[0,4,1,"cmdoption-osxphotos-export-arg-PHOTOS_LIBRARY"]},"osxphotos-help":{TOPIC:[0,4,1,"cmdoption-osxphotos-help-arg-TOPIC"]},"osxphotos-info":{"--db":[0,4,1,"cmdoption-osxphotos-info-db"],"--json":[0,4,1,"cmdoption-osxphotos-info-json"],PHOTOS_LIBRARY:[0,4,1,"cmdoption-osxphotos-info-arg-PHOTOS_LIBRARY"]},"osxphotos-keywords":{"--db":[0,4,1,"cmdoption-osxphotos-keywords-db"],"--json":[0,4,1,"cmdoption-osxphotos-keywords-json"],PHOTOS_LIBRARY:[0,4,1,"cmdoption-osxphotos-keywords-arg-PHOTOS_LIBRARY"]},"osxphotos-labels":{"--db":[0,4,1,"cmdoption-osxphotos-labels-db"],"--json":[0,4,1,"cmdoption-osxphotos-labels-json"],PHOTOS_LIBRARY:[0,4,1,"cmdoption-osxphotos-labels-arg-PHOTOS_LIBRARY"]},"osxphotos-list":{"--json":[0,4,1,"cmdoption-osxphotos-list-json"]},"osxphotos-persons":{"--db":[0,4,1,"cmdoption-osxphotos-persons-db"],"--json":[0,4,1,"cmdoption-osxphotos-persons-json"],PHOTOS_LIBRARY:[0,4,1,"cmdoption-osxphotos-persons-arg-PHOTOS_LIBRARY"]},"osxphotos-places":{"--db":[0,4,1,"cmdoption-osxphotos-places-db"],"--json":[0,4,1,"cmdoption-osxphotos-places-json"],PHOTOS_LIBRARY:[0,4,1,"cmdoption-osxphotos-places-arg-PHOTOS_LIBRARY"]},"osxphotos-query":{"--add-to-album":[0,4,1,"cmdoption-osxphotos-query-add-to-album"],"--album":[0,4,1,"cmdoption-osxphotos-query-album"],"--burst":[0,4,1,"cmdoption-osxphotos-query-burst"],"--cloudasset":[0,4,1,"cmdoption-osxphotos-query-cloudasset"],"--db":[0,4,1,"cmdoption-osxphotos-query-db"],"--deleted":[0,4,1,"cmdoption-osxphotos-query-deleted"],"--deleted-only":[0,4,1,"cmdoption-osxphotos-query-deleted-only"],"--description":[0,4,1,"cmdoption-osxphotos-query-description"],"--duplicate":[0,4,1,"cmdoption-osxphotos-query-duplicate"],"--edited":[0,4,1,"cmdoption-osxphotos-query-edited"],"--external-edit":[0,4,1,"cmdoption-osxphotos-query-external-edit"],"--favorite":[0,4,1,"cmdoption-osxphotos-query-favorite"],"--folder":[0,4,1,"cmdoption-osxphotos-query-folder"],"--from-date":[0,4,1,"cmdoption-osxphotos-query-from-date"],"--from-time":[0,4,1,"cmdoption-osxphotos-query-from-time"],"--has-comment":[0,4,1,"cmdoption-osxphotos-query-has-comment"],"--has-likes":[0,4,1,"cmdoption-osxphotos-query-has-likes"],"--has-raw":[0,4,1,"cmdoption-osxphotos-query-has-raw"],"--hdr":[0,4,1,"cmdoption-osxphotos-query-hdr"],"--hidden":[0,4,1,"cmdoption-osxphotos-query-hidden"],"--ignore-case":[0,4,1,"cmdoption-osxphotos-query-i"],"--in-album":[0,4,1,"cmdoption-osxphotos-query-in-album"],"--incloud":[0,4,1,"cmdoption-osxphotos-query-incloud"],"--is-reference":[0,4,1,"cmdoption-osxphotos-query-is-reference"],"--json":[0,4,1,"cmdoption-osxphotos-query-json"],"--keyword":[0,4,1,"cmdoption-osxphotos-query-keyword"],"--label":[0,4,1,"cmdoption-osxphotos-query-label"],"--live":[0,4,1,"cmdoption-osxphotos-query-live"],"--location":[0,4,1,"cmdoption-osxphotos-query-location"],"--max-size":[0,4,1,"cmdoption-osxphotos-query-max-size"],"--min-size":[0,4,1,"cmdoption-osxphotos-query-min-size"],"--missing":[0,4,1,"cmdoption-osxphotos-query-missing"],"--name":[0,4,1,"cmdoption-osxphotos-query-name"],"--no-comment":[0,4,1,"cmdoption-osxphotos-query-no-comment"],"--no-description":[0,4,1,"cmdoption-osxphotos-query-no-description"],"--no-likes":[0,4,1,"cmdoption-osxphotos-query-no-likes"],"--no-location":[0,4,1,"cmdoption-osxphotos-query-no-location"],"--no-place":[0,4,1,"cmdoption-osxphotos-query-no-place"],"--no-title":[0,4,1,"cmdoption-osxphotos-query-no-title"],"--not-burst":[0,4,1,"cmdoption-osxphotos-query-not-burst"],"--not-cloudasset":[0,4,1,"cmdoption-osxphotos-query-not-cloudasset"],"--not-favorite":[0,4,1,"cmdoption-osxphotos-query-not-favorite"],"--not-hdr":[0,4,1,"cmdoption-osxphotos-query-not-hdr"],"--not-hidden":[0,4,1,"cmdoption-osxphotos-query-not-hidden"],"--not-in-album":[0,4,1,"cmdoption-osxphotos-query-not-in-album"],"--not-incloud":[0,4,1,"cmdoption-osxphotos-query-not-incloud"],"--not-live":[0,4,1,"cmdoption-osxphotos-query-not-live"],"--not-missing":[0,4,1,"cmdoption-osxphotos-query-not-missing"],"--not-panorama":[0,4,1,"cmdoption-osxphotos-query-not-panorama"],"--not-portrait":[0,4,1,"cmdoption-osxphotos-query-not-portrait"],"--not-screenshot":[0,4,1,"cmdoption-osxphotos-query-not-screenshot"],"--not-selfie":[0,4,1,"cmdoption-osxphotos-query-not-selfie"],"--not-shared":[0,4,1,"cmdoption-osxphotos-query-not-shared"],"--not-slow-mo":[0,4,1,"cmdoption-osxphotos-query-not-slow-mo"],"--not-time-lapse":[0,4,1,"cmdoption-osxphotos-query-not-time-lapse"],"--only-movies":[0,4,1,"cmdoption-osxphotos-query-only-movies"],"--only-photos":[0,4,1,"cmdoption-osxphotos-query-only-photos"],"--panorama":[0,4,1,"cmdoption-osxphotos-query-panorama"],"--person":[0,4,1,"cmdoption-osxphotos-query-person"],"--place":[0,4,1,"cmdoption-osxphotos-query-place"],"--portrait":[0,4,1,"cmdoption-osxphotos-query-portrait"],"--query-eval":[0,4,1,"cmdoption-osxphotos-query-query-eval"],"--query-function":[0,4,1,"cmdoption-osxphotos-query-query-function"],"--regex":[0,4,1,"cmdoption-osxphotos-query-regex"],"--screenshot":[0,4,1,"cmdoption-osxphotos-query-screenshot"],"--selected":[0,4,1,"cmdoption-osxphotos-query-selected"],"--selfie":[0,4,1,"cmdoption-osxphotos-query-selfie"],"--shared":[0,4,1,"cmdoption-osxphotos-query-shared"],"--slow-mo":[0,4,1,"cmdoption-osxphotos-query-slow-mo"],"--time-lapse":[0,4,1,"cmdoption-osxphotos-query-time-lapse"],"--title":[0,4,1,"cmdoption-osxphotos-query-title"],"--to-date":[0,4,1,"cmdoption-osxphotos-query-to-date"],"--to-time":[0,4,1,"cmdoption-osxphotos-query-to-time"],"--uti":[0,4,1,"cmdoption-osxphotos-query-uti"],"--uuid":[0,4,1,"cmdoption-osxphotos-query-uuid"],"--uuid-from-file":[0,4,1,"cmdoption-osxphotos-query-uuid-from-file"],"-i":[0,4,1,"cmdoption-osxphotos-query-i"],PHOTOS_LIBRARY:[0,4,1,"cmdoption-osxphotos-query-arg-PHOTOS_LIBRARY"]},"osxphotos-repl":{"--db":[0,4,1,"cmdoption-osxphotos-repl-db"]},"osxphotos-tutorial":{WIDTH:[0,4,1,"cmdoption-osxphotos-tutorial-arg-WIDTH"]},"osxphotos.PhotoInfo":{"export":[3,2,1,""],ExifInfo:[3,0,1,""],ExportResults:[3,0,1,""],ScoreInfo:[3,0,1,""],SearchInfo:[3,0,1,""],adjustments:[3,3,1,""],album_info:[3,3,1,""],albums:[3,3,1,""],asdict:[3,2,1,""],burst:[3,3,1,""],burst_album_info:[3,3,1,""],burst_albums:[3,3,1,""],burst_default_pick:[3,3,1,""],burst_key:[3,3,1,""],burst_photos:[3,3,1,""],burst_selected:[3,3,1,""],comments:[3,3,1,""],date:[3,3,1,""],date_added:[3,3,1,""],date_modified:[3,3,1,""],date_trashed:[3,3,1,""],description:[3,3,1,""],duplicates:[3,3,1,""],exif_info:[3,3,1,""],exiftool:[3,3,1,""],export2:[3,2,1,""],external_edit:[3,3,1,""],face_info:[3,3,1,""],favorite:[3,3,1,""],filename:[3,3,1,""],has_raw:[3,3,1,""],hasadjustments:[3,3,1,""],hdr:[3,3,1,""],height:[3,3,1,""],hidden:[3,3,1,""],import_info:[3,3,1,""],incloud:[3,3,1,""],intrash:[3,3,1,""],iscloudasset:[3,3,1,""],ismissing:[3,3,1,""],ismovie:[3,3,1,""],isphoto:[3,3,1,""],israw:[3,3,1,""],isreference:[3,3,1,""],json:[3,2,1,""],keywords:[3,3,1,""],labels:[3,3,1,""],labels_normalized:[3,3,1,""],likes:[3,3,1,""],live_photo:[3,3,1,""],location:[3,3,1,""],orientation:[3,3,1,""],original_filename:[3,3,1,""],original_filesize:[3,3,1,""],original_height:[3,3,1,""],original_orientation:[3,3,1,""],original_width:[3,3,1,""],panorama:[3,3,1,""],path:[3,3,1,""],path_derivatives:[3,3,1,""],path_edited:[3,3,1,""],path_edited_live_photo:[3,3,1,""],path_live_photo:[3,3,1,""],path_raw:[3,3,1,""],person_info:[3,3,1,""],persons:[3,3,1,""],place:[3,3,1,""],portrait:[3,3,1,""],raw_original:[3,3,1,""],render_template:[3,2,1,""],score:[3,3,1,""],screenshot:[3,3,1,""],search_info:[3,3,1,""],search_info_normalized:[3,3,1,""],selfie:[3,3,1,""],shared:[3,3,1,""],slow_mo:[3,3,1,""],time_lapse:[3,3,1,""],title:[3,3,1,""],tzoffset:[3,3,1,""],uti:[3,3,1,""],uti_edited:[3,3,1,""],uti_original:[3,3,1,""],uti_raw:[3,3,1,""],uuid:[3,3,1,""],visible:[3,3,1,""],width:[3,3,1,""]},"osxphotos.PhotoInfo.ExifInfo":{aperture:[3,1,1,""],bit_rate:[3,1,1,""],camera_make:[3,1,1,""],camera_model:[3,1,1,""],codec:[3,1,1,""],duration:[3,1,1,""],exposure_bias:[3,1,1,""],flash_fired:[3,1,1,""],focal_length:[3,1,1,""],fps:[3,1,1,""],iso:[3,1,1,""],latitude:[3,1,1,""],lens_model:[3,1,1,""],longitude:[3,1,1,""],metering_mode:[3,1,1,""],sample_rate:[3,1,1,""],shutter_speed:[3,1,1,""],track_format:[3,1,1,""],white_balance:[3,1,1,""]},"osxphotos.PhotoInfo.ExportResults":{all_files:[3,2,1,""]},"osxphotos.PhotoInfo.ScoreInfo":{behavioral:[3,1,1,""],curation:[3,1,1,""],failure:[3,1,1,""],harmonious_color:[3,1,1,""],highlight_visibility:[3,1,1,""],immersiveness:[3,1,1,""],interaction:[3,1,1,""],interesting_subject:[3,1,1,""],intrusive_object_presence:[3,1,1,""],lively_color:[3,1,1,""],low_light:[3,1,1,""],noise:[3,1,1,""],overall:[3,1,1,""],pleasant_camera_tilt:[3,1,1,""],pleasant_composition:[3,1,1,""],pleasant_lighting:[3,1,1,""],pleasant_pattern:[3,1,1,""],pleasant_perspective:[3,1,1,""],pleasant_post_processing:[3,1,1,""],pleasant_reflection:[3,1,1,""],pleasant_symmetry:[3,1,1,""],promotion:[3,1,1,""],sharply_focused_subject:[3,1,1,""],tastefully_blurred:[3,1,1,""],well_chosen_subject:[3,1,1,""],well_framed_subject:[3,1,1,""],well_timed_shot:[3,1,1,""]},"osxphotos.PhotoInfo.SearchInfo":{activities:[3,3,1,""],all:[3,3,1,""],asdict:[3,2,1,""],bodies_of_water:[3,3,1,""],city:[3,3,1,""],country:[3,3,1,""],holidays:[3,3,1,""],labels:[3,3,1,""],locality_names:[3,3,1,""],media_types:[3,3,1,""],month:[3,3,1,""],neighborhoods:[3,3,1,""],place_names:[3,3,1,""],season:[3,3,1,""],state:[3,3,1,""],state_abbreviation:[3,3,1,""],streets:[3,3,1,""],venue_types:[3,3,1,""],venues:[3,3,1,""],year:[3,3,1,""]},"osxphotos.PhotosDB":{album_info:[3,3,1,""],album_info_shared:[3,3,1,""],albums:[3,3,1,""],albums_as_dict:[3,3,1,""],albums_shared:[3,3,1,""],albums_shared_as_dict:[3,3,1,""],db_path:[3,3,1,""],db_version:[3,3,1,""],folder_info:[3,3,1,""],folders:[3,3,1,""],get_db_connection:[3,2,1,""],get_photo:[3,2,1,""],import_info:[3,3,1,""],keywords:[3,3,1,""],keywords_as_dict:[3,3,1,""],labels:[3,3,1,""],labels_as_dict:[3,3,1,""],labels_normalized:[3,3,1,""],labels_normalized_as_dict:[3,3,1,""],library_path:[3,3,1,""],person_info:[3,3,1,""],persons:[3,3,1,""],persons_as_dict:[3,3,1,""],photos:[3,2,1,""],photos_by_uuid:[3,2,1,""],query:[3,2,1,""]},osxphotos:{"--db":[0,4,1,"cmdoption-osxphotos-db"],"--json":[0,4,1,"cmdoption-osxphotos-json"],"--version":[0,4,1,"cmdoption-osxphotos-v"],"-v":[0,4,1,"cmdoption-osxphotos-v"],PhotoInfo:[3,0,1,""],PhotosDB:[3,0,1,""]}},objnames:{"0":["py","class","Python class"],"1":["py","attribute","Python attribute"],"2":["py","method","Python method"],"3":["py","property","Python property"],"4":["std","cmdoption","program option"]},objtypes:{"0":"py:class","1":"py:attribute","2":"py:method","3":"py:property","4":"std:cmdoption"},terms:{"0":[0,1,3],"00":0,"01":0,"048576mb":0,"07":0,"1":[0,1,3],"10":1,"1048576":0,"11":1,"12":[0,1],"120":3,"12t12":0,"15":1,"1gb":1,"2":[0,1],"20":0,"2000":0,"2001":0,"2019":0,"3":[0,1],"31":0,"4":3,"5":[0,3],"6":1,"7":1,"8601":0,"boolean":3,"byte":[0,3],"case":[0,3],"class":[0,3],"default":[0,3],"do":[0,1,3],"export":3,"float":3,"function":[0,3],"import":[0,1,3],"int":3,"long":3,"new":[0,1,3],"public":3,"return":[0,3],"true":[1,3],A:[0,3],AND:[0,3],Be:0,By:0,For:[0,3],If:[0,1,3],In:[0,3],OR:[0,3],The:[0,1,3],Then:1,These:0,To:[0,1],__main__:1,__name__:1,_bearbeiten:0,_edit:0,_low_r:0,_origin:0,_preview:[0,3],abbrevi:3,abil:1,abl:1,about:[1,3],abov:1,absolut:3,access:[1,3],accord:1,activ:3,actual:[0,3],ad:[0,3],add:0,addit:[0,3],adjust:3,adjustmentsinfo:3,adob:0,advanc:1,advantag:0,after:0,against:3,ahead:3,aka:1,album:3,album_info:3,album_info_shar:3,album_nam:1,albuminfo:3,albums_as_dict:[1,3],albums_shar:3,albums_shared_as_dict:3,alic:1,all:[0,3],all_fil:3,allow:[0,1],alpha:0,alreadi:[0,3],also:[0,1,3],altern:[0,1],an:[0,3],ani:[0,1,3],apertur:3,apf:0,app:[0,1,3],append:[0,3],appl:[0,1,3],applescript:[0,3],appli:[0,3],applic:1,ar:[0,3],aren:[1,3],arg:[0,1,3],argument:[0,1],asdict:3,asset:3,associ:[0,1,3],assum:3,asynchroun:3,attempt:[0,1,3],attribut:[0,3],author:0,auto:1,automat:0,awar:3,bar:1,base:3,beach:0,becaus:1,been:[0,1,3],befor:[0,1],begin:0,behavior:[0,3],being:0,below:[0,1],best:[0,3],beta:1,bit:3,bit_rat:3,blob:0,bodi:3,bodies_of_wat:3,bool:3,both:[0,1],brew:1,built:1,burst:[0,3],burst_album:3,burst_album_info:3,burst_default_pick:3,burst_kei:3,burst_photo:3,burst_select:3,call:[0,1,3],callabl:3,camera:[0,3],camera_mak:3,camera_model:3,can:[0,1],cannot:[0,3],canon:3,care:1,categor:3,categori:0,caution:0,cd:1,certain:0,chang:[0,3],charact:1,citi:3,classif:[0,1],cleanup:0,cli:1,click:1,clone:[0,1],cloud:3,cloudasset:0,code:1,codec:3,collis:0,com:[0,1,3],comfort:1,command_nam:1,comment:[0,3],compar:[0,3],compat:[0,1],complex:0,compon:0,comprehens:0,compress:[0,3],comput:3,config:0,conform:3,conjunct:0,connect:[0,3],consist:0,contact:1,contain:[0,1,3],context:0,convert:[0,3],convert_to_jpeg:3,converted_to_jpeg:[0,3],coordin:0,copi:[0,3],copyright:0,correct:3,correspond:0,could:[0,1],count:3,countri:3,cr2:3,creat:0,created:0,creation:3,creationd:0,creator:0,criteria:[0,3],csv:0,curat:3,current:[0,3],cursor:3,dai:0,data:[0,3],databas:[0,1,3],date:[0,3],date_ad:3,date_modifi:3,date_trash:3,datecr:0,datetim:3,datetimeorigin:[0,3],db:[0,1,3],db_path:3,db_version:3,dbfile:3,def:1,default_album:1,degre:3,delet:[0,3],deleted_directori:3,deleted_fil:3,deriv:3,desc:0,descend:3,descr:0,describ:1,descript:[0,1,3],description_templ:3,dest:[0,3],dest_dir:1,destin:[1,3],detail:[0,3],determin:3,dict:3,did:0,differ:[0,1,3],digikam:0,dir:1,direct:0,directli:[0,1],directori:0,disabl:1,disk:[0,3],displai:[0,1],doc:0,document:0,doe:[0,1,3],doesn:0,don:0,download:[0,1,3],drive:0,drop:[0,3],dry:[0,3],dry_run:3,dump:1,duplic:[0,1,3],durat:3,dure:[0,3],dynam:0,e:[0,1,3],each:[0,3],earlier:3,easiest:1,easili:1,echo:[0,1],edit:[0,1,3],edited_filenam:3,edited_nam:1,editor:[0,3],either:[0,1,3],els:[1,3],embed:0,empti:3,end:[0,3],ensur:[1,3],entir:3,environ:1,equival:0,error:[0,3],error_str:3,etc:[0,1,3],eval:0,evalu:0,even:[0,3],exact:3,exampl:[0,3],except:3,execut:1,exif:[0,3],exif_info:3,exif_upd:[0,3],exifinfo:3,exiftool:[0,1,3],exiftool_error:3,exiftool_flag:3,exiftool_path:0,exiftool_pod:0,exiftool_warn:3,exiftoolcach:3,exist:[0,1,3],exit:[0,1],expandus:1,expect:0,experi:0,experiment:0,export2:3,export_as_hardlink:3,export_db:3,export_dir:0,export_path:1,exportdb:0,exportdb_abc:3,exportdb_fil:0,exportdbinmemori:3,exported_album:3,exportresult:3,exposure_bia:3,express:0,ext:0,extend:[0,3],extens:[0,3],extern:[0,3],external_edit:3,extract:3,f:[0,1,3],face:[0,1,3],face_info:3,faceinfo:3,fail:3,failur:3,fals:3,familiar:1,faster:[0,3],favorit:[0,3],featur:0,field:3,file:[0,3],filenam:[0,1,3],filename_origin:0,filepath:[0,1],files:3,fileutil:3,fileutilabc:3,fileutilnoop:3,filter:0,find:0,finder:0,findercom:0,first:[0,3],flag:[0,3],flash_fir:3,flexibl:1,focal_length:3,folder:[0,3],folder_album:0,folder_info:3,folderinfo:3,follow:[0,1],foo:1,forc:[0,3],form:0,format:[0,1,3],found:[0,1,3],fp:3,from:[0,3],from_dat:[0,3],from_tim:0,front:[0,3],full:[0,3],g:[0,1,3],gener:[0,3],geoloc:[0,3],get:[1,3],get_db_connect:3,get_photo:3,github:[0,1],give:0,gp:0,gpscoordin:0,gpslatitud:0,gpslatituderef:0,gpslongitud:0,gpslongituderef:0,gpsposit:0,gpu:0,group:[0,3],h:1,ha:[0,1,3],had:0,handl:0,happen:0,hardlink:[0,3],harmonious_color:3,has_raw:3,hasadjust:[1,3],hash:0,have:[0,1,3],haven:0,hdr:[0,3],headlin:0,heic:0,height:[0,3],help:1,here:0,hidden:[0,3],high:0,highli:0,highlight_vis:3,hold:3,holidai:3,homebrew:1,how:0,html:0,http:[0,1,3],i:[0,1,3],icloud:[0,3],identifi:[0,3],ignor:[0,3],ignore_date_modifi:3,ignore_signatur:3,imag:[0,1,3],imagedescript:0,img_1234:[0,3],immedi:3,immers:3,implement:0,import_info:3,importinfo:3,incloud:[0,3],includ:[0,1,3],incorrect:3,increment:3,index:1,info:[1,3],inform:[0,1,3],initi:3,insensit:0,instal:[0,3],instanc:3,instead:[0,3],instruct:1,integ:0,intend:0,interact:[0,1,3],interesting_subject:3,intermitt:0,internet:0,intrash:3,intrusive_object_pres:3,invalid:[1,3],io:0,iptc:[0,3],is_valid_filepath:1,iscloudasset:3,isdir:1,ismiss:[1,3],ismovi:3,iso:[0,3],isphoto:3,israw:3,isrefer:3,item:[0,3],iterm2:0,j:[0,3],john:1,join:1,jpeg:[0,3],jpeg_ext:3,jpeg_qual:[0,3],jpg:[0,3],json:[0,3],just:1,kei:3,keyword:3,keyword_templ:3,keywords_as_dict:[1,3],know:[0,3],label:3,labels_as_dict:3,labels_norm:3,labels_normalized_as_dict:3,laps:[0,3],larg:[0,1],largest:3,last:[0,1],later:0,latest:1,latitud:3,lead:[0,3],learn:3,lens_model:3,level:[0,3],librari:[0,3],library_path:[1,3],licens:[0,1],lightroom:0,liglob:3,like:[0,1,3],list:[1,3],live:[0,3],live_photo:3,lively_color:3,load:0,local:3,locality_nam:3,locat:[0,3],log:3,longitud:3,look:[0,1,3],lot:0,low_light:3,lower:0,m:[0,1,3],mac:[0,1],machin:[0,1,3],maco:[0,1],mai:[0,3],main:1,make:1,makedir:1,mangl:3,mani:0,mark:[0,3],master:0,match:[0,3],max:0,maximum:[0,3],me:1,mean:3,media:3,media_typ:[1,3],memori:3,merg:[0,3],merge_exif_keyword:3,merge_exif_person:3,messag:1,metadata:[0,3],metering_mod:3,method:3,mib:0,microsoft:0,might:3,min:[0,1],minor:0,miss:[0,1,3],missing_album:3,mo:0,mode:[0,3],modif:[0,3],modifi:0,modifyd:[0,3],modul:1,monterei:1,month:3,more:[0,1,3],most:0,motion:[0,3],mov:[0,3],movi:[0,1,3],multi:0,multipl:[0,1],must:[0,3],mysteri:3,naiv:3,name:[0,3],need:1,neighborhood:3,neither:[0,1],network:0,nist:0,nois:3,non:[0,3],none:[1,3],nor:0,normal:[0,3],note:[0,1,3],notic:3,now:1,object:[0,3],obvious:0,occur:[0,3],offset:3,offsettimeorigin:0,older:3,one:[0,3],onli:[0,3],op:3,open:[0,1],option:[0,1,3],order:[0,1,3],org:[0,3],organ:0,orient:3,origin:[0,1,3],original_filenam:[1,3],original_files:3,original_height:3,original_nam:0,original_orient:3,original_width:3,os:1,osxphotos_export:0,other:[0,1,3],otherwis:[1,3],out:[0,1],output:[0,3],outsid:3,overal:3,overrid:0,overwrit:[0,3],own:[0,1],p:1,page:1,pair:[0,3],panorama:[0,3],paramet:[1,3],part:[0,3],particip:0,particular:3,pass:[0,3],path:[0,1,3],path_deriv:3,path_edit:[1,3],path_edited_live_photo:3,path_live_photo:3,path_raw:3,pathlib:1,pathvalid:1,per:0,permit:0,person:3,person_info:3,personinfo:3,personinimag:0,persons_as_dict:[1,3],photo:[0,3],photo_ext:0,photo_filenam:0,photoinfo:[0,3],photokit:0,photonam:0,photoname_bearbeiten:0,photoname_edit:0,photoname_low_r:0,photoname_preview:0,photos_by_uuid:3,photos_librari:[0,1],photosdb:[1,3],photoslibrari:[0,1],phototempl:3,pictur:[0,1,3],pixel:3,place:[1,3],place_nam:3,placeinfo:3,platform:1,pleas:1,pleasant_camera_tilt:3,pleasant_composit:3,pleasant_light:3,pleasant_pattern:3,pleasant_perspect:3,pleasant_post_process:3,pleasant_reflect:3,pleasant_symmetri:3,png:0,portrait:[0,3],posit:[0,1],possibl:[0,3],post:0,pre:1,preced:0,present:0,preview:[0,3],preview_suffix:3,previous:0,primari:0,print:[0,3],process:[0,3],produc:3,project:[0,1],promot:3,properti:3,provid:[0,1,3],py:[0,1],pylint:1,pypi:1,python3:1,python:[0,1],qualiti:[0,3],queri:[1,3],query_funct:0,queryopt:3,quickli:0,quicktim:[0,3],rais:3,rang:[0,3],rate:0,raw:[0,3],raw_origin:3,raw_photo:3,re:0,read:[1,3],readabl:3,recent:[0,3],recommend:1,refer:[0,1,3],referenc:0,regardless:3,regex:0,region:0,regular:0,relat:3,releas:1,reliabl:0,remov:0,renam:0,render:[0,3],render_opt:3,render_templ:3,rendered_str:3,renderopt:3,repeat:0,repl:1,replac:[0,3],replace_keyword:3,repo:1,report:0,repres:[0,3],represent:3,requir:[0,1,3],resolut:0,result:[0,3],retri:0,reus:0,revers:[0,3],rhettbul:[0,1],run:[0,1,3],s:[0,3],same:[0,3],sample_r:3,sanitize_filepath:1,save:0,score:3,scoreinfo:3,screenshot:[0,3],script:0,search:[0,1,3],search_info:3,search_info_norm:3,searchinfo:3,season:3,second:3,see:[0,1,3],select:[0,3],self:3,selfi:[0,3],session:3,set:[0,3],setup:1,setuptool:1,sh:1,share:[0,3],sharply_focused_subject:3,shell:[0,1],shell_quot:0,should:[0,1],show:[0,1,3],shutter_spe:3,si:0,sidecar:[0,3],sidecar_drop_ext:3,sidecar_exiftool:3,sidecar_exiftool_skip:[0,3],sidecar_exiftool_written:[0,3],sidecar_ext:0,sidecar_json:3,sidecar_json_skip:[0,3],sidecar_json_written:[0,3],sidecar_xmp:3,sidecar_xmp_skip:[0,3],sidecar_xmp_written:[0,3],sierra:1,signatur:[0,3],silent:3,simpl:1,simpli:1,singl:[0,3],size:[0,1,3],skip:[0,1,3],skipped_album:3,slow:[0,3],slow_mo:3,smith:1,so:3,some:3,someth:3,sort:3,sourc:3,space:0,specif:[1,3],specifi:[0,3],spotlight:[0,1],sqlite3:3,stack:3,start:[0,1],state:[0,3],state_abbrevi:3,statu:[0,3],still:3,storat:3,store:[0,3],str:3,street:3,string:[0,3],strip:0,subfold:0,subject:0,subsequ:0,suffix:[0,3],suppli:3,sur:1,sure:[0,1],sy:1,synch:[0,3],system:0,t:[0,1,3],tabl:3,tag:[0,1,3],tagnam:0,tagslist:0,take:1,taken:0,tastefully_blur:3,templat:[0,1,3],template_str:3,term:3,termin:[0,1],test:[0,1],text:3,than:[0,3],thei:[0,3],them:[0,3],thi:[0,1,3],through:1,thu:[0,1],time:[0,3],time_laps:3,timecr:0,timeout:3,timezon:[0,3],titl:[0,1,3],to_dat:[0,3],to_tim:0,todai:0,toml:0,top:[0,3],topic:0,touch:[0,3],touch_fil:3,town:3,track_format:3,trail:0,trash:3,treat:[0,3],tupl:3,tutori:1,two:0,txt:0,type:[0,3],tzoffset:3,unedit:1,unfil:1,uniform:[0,3],unit:0,unless:3,unlik:0,unmatch:3,until:3,up:0,updat:[0,3],upon:[0,3],us:[0,3],use_albums_as_keyword:3,use_persons_as_keyword:3,use_photokit:3,use_photos_export:3,useabl:0,user:3,userdata:0,utc:3,uti:[0,3],uti_edit:3,uti_origin:3,uti_raw:3,util:[1,3],uuid:[0,1,3],v:[0,1],vacat:3,valid:[0,1,3],valu:[0,1,3],variou:3,ve:[0,3],venu:3,venue_typ:3,verbos:[0,3],veri:1,verifi:1,versa:1,version:[0,1,3],via:[0,1,3],vice:1,video:[0,3],virtual:[0,1],visbl:3,visibl:3,volum:0,wa:[0,3],wai:1,want:[0,1],warn:[0,1,3],water:3,wed:3,well:[0,1,3],well_chosen_subject:3,well_framed_subject:3,well_timed_shot:3,went:0,were:[0,3],what:[0,3],when:[0,3],where:0,whether:0,which:[0,1,3],white_bal:3,whitespac:0,whose:0,width:[0,3],within:0,without:0,work:[0,1,3],would:[0,1],write:[0,3],written:[0,3],x:1,xattr:0,xattr_skip:3,xattr_written:3,xmp:[0,3],year:[0,3],yet:3,you:[0,1,3],your:[0,1]},titles:["osxphotos command line interface (CLI)","Welcome to osxphotos\u2019s documentation!","osxphotos","osxphotos package"],titleterms:{"200mb":1,"4":1,"5":1,"default":1,"export":[0,1],about:0,add:1,album:[0,1],all:1,ar:1,awail:1,base:1,big:1,chang:1,cli:0,command:[0,1],countri:1,creat:1,creation:1,date:1,desktop:1,digit:1,directori:1,document:1,dump:0,exampl:1,exif:1,file:1,find:1,folder:1,from:1,full:1,git:1,group:1,help:0,higher:1,indic:1,info:0,instal:1,interfac:[0,1],json:1,keyword:[0,1],kid:1,label:[0,1],larger:1,librari:1,line:[0,1],list:0,media:1,metadata:1,modul:3,month:1,name:1,necessari:1,nocountri:1,onli:1,oper:1,osxphoto:[0,1,2,3],ouput:1,output:1,packag:[1,3],person:[0,1],photo:1,pip:1,pipx:1,place:0,print:1,queri:0,repl:0,repositori:1,result:1,s:1,specifi:1,structur:1,support:1,system:1,tabl:1,than:1,them:1,tutori:0,type:1,updat:1,us:1,usag:1,verbos:1,video:1,welcom:1,what:1,when:1,write:1,year:1}}) \ No newline at end of file diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 00000000..3ad7e2b6 --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,5 @@ +sphinx_click +pytest==6.2.4 +pytest-mock +m2r2 +