From 17ac5949e15057379eb13b979d4d7498bbb94d67 Mon Sep 17 00:00:00 2001 From: Rhet Turnbull Date: Sun, 14 Mar 2021 12:16:26 -0700 Subject: [PATCH] Updated docs for --cleanup, #394 --- README.md | 150 +- docs/.buildinfo | 2 +- docs/_modules/index.html | 4 +- .../photoinfo/_photoinfo_export.html | 148 +- docs/_static/doctools.js | 7 +- docs/_static/documentation_options.js | 2 +- docs/_static/language_data.js | 4 +- docs/_static/pygments.css | 6 +- docs/_static/searchtools.js | 26 +- docs/_static/underscore-1.12.0.js | 2027 +++++++++++++++++ docs/_static/underscore.js | 37 +- docs/cli.html | 420 ++-- docs/genindex.html | 19 +- docs/index.html | 4 +- docs/modules.html | 4 +- docs/objects.inv | Bin 3209 -> 3219 bytes docs/osxphotos.pdf | Bin 246525 -> 247669 bytes docs/reference.html | 359 +-- docs/search.html | 4 +- docs/searchindex.js | 2 +- osxphotos/cli.py | 5 +- 21 files changed, 2753 insertions(+), 477 deletions(-) create mode 100644 docs/_static/underscore-1.12.0.js diff --git a/README.md b/README.md index 80356fbd..9c26bde2 100644 --- a/README.md +++ b/README.md @@ -183,41 +183,52 @@ Options: use in the following order: 1. last opened library, 2. system library, 3. ~/Pictures/Photos Library.photoslibrary + -V, --verbose Print verbose output. --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 Search for photos with person PERSON. If more than one person, treated as "OR", e.g. find photos matching any person + --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 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) + --uuid UUID Search for photos with UUID(s). --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 Search for TITLE in title of photo. --no-title Search for photos with no title. --description DESC Search for DESC in description of photo. --no-description Search for photos with no description. --place PLACE Search for PLACE in photo's reverse geolocation info + --no-place Search for photos with no associated place name info (no reverse geolocation info) + --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 Search for photos whose uniform type identifier (UTI) matches UTI + -i, --ignore-case Case insensitive search for title, description, place, keyword, person, or album. + --edited Search for photos that have been edited. --external-edit Search for photos edited in external editor. --favorite Search for photos marked favorite. @@ -226,47 +237,61 @@ Options: --not-hidden Search for photos not marked hidden. --shared Search for photos in shared iCloud album (Photos 5 only). + --not-shared Search for photos not in shared iCloud album (Photos 5 only). + --burst Search for photos that were taken in a burst. --not-burst Search for photos that are not part of a burst. + --live Search for Apple live photos --not-live Search for photos that are not Apple live photos. + --portrait Search for Apple portrait mode photos. --not-portrait Search for photos that are not Apple portrait mode photos. + --screenshot Search for screenshot photos. --not-screenshot Search for photos that are not screenshot photos. + --slow-mo Search for slow motion videos. --not-slow-mo Search for photos that are not slow motion videos. + --time-lapse Search for time lapse videos. --not-time-lapse Search for photos that are not time lapse videos. + --hdr Search for high dynamic range (HDR) photos. --not-hdr Search for photos that are not HDR photos. --selfie Search for selfies (photos taken with front- facing cameras). + --not-selfie Search for photos that are not selfies. --panorama Search for panorama photos. --not-panorama Search for photos that are not panoramas. --has-raw Search for photos with both a jpeg and raw version + --only-movies Search only for movies (default searches both images and movies). + --only-photos Search only for photos/images (default searches both images and movies). + --from-date DATETIME Search by start item date, e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601). + --to-date DATETIME Search by end item date, e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601). + --has-comment Search for photos that have comments. --no-comment Search for photos with no comments. --has-likes Search for photos that have likes. @@ -274,17 +299,23 @@ Options: --is-reference Search for photos that were imported as referenced files (not copied into Photos library). + --in-album Search for photos that are in one or more albums. + --not-in-album Search for photos that are not in any albums. --missing Export only photos missing from the Photos library; must be used with --download-missing. + --deleted Include photos from the 'Recently Deleted' folder. + --deleted-only Include only photos from the 'Recently Deleted' folder. + --update Only export new or updated files. See notes below on export and --update. + --ignore-signature When used with '--update', ignores file signature when updating files. This is useful if you have processed or edited exported @@ -303,12 +334,15 @@ Options: 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 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 (test) the export but don't actually export any files; most useful with --verbose. + --export-as-hardlink Hardlink files instead of copying them. Cannot be used with --exiftool which creates copies of the files with embedded EXIF data. Note: on @@ -316,41 +350,53 @@ Options: giving many of the same advantages as hardlinks without having to use --export-as- hardlink. + --touch-file Sets the file's modification time to match photo date. + --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) + --export-by-date Automatically create output folders to organize photos by date created (e.g. DEST/2019/12/20/photoname.jpg). + --skip-edited Do not export edited version of photo if an edited version exists. + --skip-original-if-edited Do not export original if there is an edited version (exports only the edited version). + --skip-bursts Do not export all associated burst images in the library if a photo is a burst photo. + --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). + --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. + --jpeg-quality FLOAT RANGE 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 Attempt to download missing photos from iCloud. The current implementation uses Applescript to interact with Photos to export @@ -363,6 +409,7 @@ Options: export all burst images; only the primary photo will be exported--associated burst images will be skipped. + --sidecar FORMAT Create sidecar for each photo exported; valid FORMAT values: xmp, json, exiftool; --sidecar xmp: create XMP sidecar used by Digikam, Adobe @@ -389,6 +436,7 @@ Options: tags exported in the JSON and exiftool sidecar, see '--exiftool'. See also '--ignore- signature'. + --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', @@ -400,6 +448,7 @@ Options: of different types but the same name in the output directory, e.g. 'IMG_1234.JPG' and 'IMG_1234.MOV'. + --exiftool Use exiftool to write metadata directly to exported photos. To use this option, exiftool must be installed and in the path. exiftool @@ -421,8 +470,10 @@ Options: QuickTime:ModifyDate (see also --ignore-date- modified); QuickTime:GPSCoordinates; UserData:GPSCoordinates. + --exiftool-path EXIFTOOL_PATH Optionally specify path to exiftool; if not provided, will look for exiftool in $PATH. + --exiftool-option OPTION Optional flag/option to pass to exiftool when using --exiftool. For example, --exiftool- option '-m' to ignore minor warnings. Specify @@ -432,21 +483,27 @@ Options: 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 Merge any keywords found in the original file with keywords used for '--exiftool' and '-- sidecar'. + --exiftool-merge-persons Merge any persons found in the original file with persons used for '--exiftool' and '-- sidecar'. + --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 Use person in image as keyword/tag when exporting metadata. + --album-keyword Use album name as keyword/tag when exporting metadata. + --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 @@ -459,6 +516,7 @@ Options: "{folder_album}" --keyword-template "{created.year}". See '--replace-keywords' and Templating System below. + --replace-keywords Replace keywords with any values specified with --keyword-template. By default, --keyword-template will add keywords to any @@ -467,6 +525,7 @@ Options: from --keyword-template will replace any existing keywords instead of adding additional keywords. + --description-template TEMPLATE For use with --exiftool, --sidecar; specify a template string to use as description in the @@ -477,6 +536,7 @@ Options: --description-template "{descr} exported with osxphotos on {today.date}" See Templating System below. + --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, '-- @@ -485,11 +545,13 @@ Options: TEMPLATE values by using '--finder-tag- template' multiple times. See also '--finder- tag-keywords and Extended Attributes below.'. + --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', @@ -500,16 +562,19 @@ Options: findercomment "{title}; {descr}" See Extended Attributes below for additional details on this option. + --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 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 Specify file extension for JPEG files. Photos uses .jpeg for edited images but many images are imported with .jpg or .JPG which can @@ -519,12 +584,14 @@ Options: exported JPEG images. Valid values are jpeg, jpg, JPEG, JPG; e.g. '--jpeg-ext jpg' to use '.jpg' for all JPEGs. + --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 Optional suffix template for naming edited photos. Default name for edited photos is in form 'photoname_edited.ext'. For example, with @@ -534,6 +601,7 @@ Options: suffix is '_edited'. Multi-value templates (see Templating System) are not permitted with --edited-suffix. + --original-suffix SUFFIX Optional suffix template for naming original photos. Default name for original photos is in form 'filename.ext'. For example, with '-- @@ -542,9 +610,11 @@ Options: default suffix is '' (no suffix). Multi-value templates (see Templating System) are not permitted with --original-suffix. + --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 with '--download-missing' or '--use- photos-export' to use direct Photos interface instead of AppleScript to export. Highly @@ -552,14 +622,23 @@ Options: iTerm2 (use with Terminal.app). This is faster and more reliable than the default AppleScript interface. + --report Write a CSV formatted report of all files that were exported. + --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. + 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. + --exportdb EXPORTDB_FILE Specify alternate name for database file which stores state information for export and --update. If --exportdb is not specified, @@ -568,6 +647,7 @@ Options: directory. Must be specified as filename only, not a path, as export database will be saved in export directory. + --load-config Load options from file as written with --save- config. This allows you to save a complex @@ -579,9 +659,11 @@ Options: line options are used in conjunction with --load-config, they will override the corresponding values in the config file. + --save-config Save options to file for use with --load- config. File format is TOML. + --help Show this message and exit. ** Export ** @@ -636,25 +718,32 @@ The following attributes may be used with '--xattr-template': authors The author, or authors, of the contents of the file. A list of strings. (com.apple.metadata:kMDItemAuthors) + comment A comment related to the file. This differs from the Finder comment, kMDItemFinderComment. A string. (com.apple.metadata:kMDItemComment) + copyright The copyright owner of the file contents. A string. (com.apple.metadata:kMDItemCopyright) + description A description of the content of the resource. The description may include an abstract, table of contents, reference to a graphical representation of content or a free-text account of the content. A string. (com.apple.metadata:kMDItemDescription) + findercomment Finder comments for this file. A string. (com.apple.metadata:kMDItemFinderComment) + headline A publishable entry providing a synopsis of the contents of the file. A string. (com.apple.metadata:kMDItemHeadline) + keywords Keywords associated with this file. For example, “Birthday”, “Important”, etc. This differs from Finder tags (_kMDItemUserTags) which are keywords/tags shown in the Finder and searchable in Spotlight using "tag:tag_name". A list of strings. (com.apple.metadata:kMDItemKeywords) + For additional information on extended attributes see: https://developer.apple.c om/documentation/coreservices/file_metadata/mditem/common_metadata_attribute_key s @@ -820,6 +909,7 @@ Substitution Description {name} Current filename of the photo {original_name} Photo's original filename when imported to Photos + {title} Title of the photo {descr} Description of the photo {media_type} Special media type resolved in this @@ -829,30 +919,41 @@ Substitution Description 'video' if no special type. Customize one or more media types using format: '{media_type,vi deo=vidéo;time_lapse=vidéo_accélérée}' + {photo_or_video} 'photo' or 'video' depending on what type the image is. To customize, use default value as in '{photo_or_video,photo=fotos;video=videos}' + {hdr} Photo is HDR?; True/False value, use in format '{hdr?VALUE_IF_TRUE,VALUE_IF_FALSE}' + {edited} Photo has been edited (has adjustments)?; True/False value, use in format '{edited?VALUE_IF_TRUE,VALUE_IF_FALSE}' + {created.date} Photo's creation date in ISO format, e.g. '2020-03-22' + {created.year} 4-digit year of photo creation time {created.yy} 2-digit year of photo creation time {created.mm} 2-digit month of the photo creation time (zero padded) + {created.month} Month name in user's locale of the photo creation time + {created.mon} Month abbreviation in the user's locale of the photo creation time + {created.dd} 2-digit day of the month (zero padded) of photo creation time + {created.dow} Day of week in user's locale of the photo creation time + {created.doy} 3-digit day of year (e.g Julian day) of photo creation time, starting from 1 (zero padded) + {created.hour} 2-digit hour of the photo creation time {created.min} 2-digit minute of the photo creation time {created.sec} 2-digit second of the photo creation time @@ -865,38 +966,51 @@ Substitution Description no template will return null value. See https://strftime.org/ for help on strftime templates. + {modified.date} Photo's modification date in ISO format, e.g. '2020-03-22'; uses creation date if photo is not modified + {modified.year} 4-digit year of photo modification time; uses creation date if photo is not modified + {modified.yy} 2-digit year of photo modification time; uses creation date if photo is not modified + {modified.mm} 2-digit month of the photo modification time (zero padded); uses creation date if photo is not modified + {modified.month} Month name in user's locale of the photo modification time; uses creation date if photo is not modified + {modified.mon} Month abbreviation in the user's locale of the photo modification time; uses creation date if photo is not modified + {modified.dd} 2-digit day of the month (zero padded) of the photo modification time; uses creation date if photo is not modified + {modified.dow} Day of week in user's locale of the photo modification time; uses creation date if photo is not modified + {modified.doy} 3-digit day of year (e.g Julian day) of photo modification time, starting from 1 (zero padded); uses creation date if photo is not modified + {modified.hour} 2-digit hour of the photo modification time; uses creation date if photo is not modified + {modified.min} 2-digit minute of the photo modification time; uses creation date if photo is not modified + {modified.sec} 2-digit second of the photo modification time; uses creation date if photo is not modified + {modified.strftime} Apply strftime template to file modification date/time. Should be used in form {modified.strftime,TEMPLATE} where TEMPLATE is @@ -907,21 +1021,28 @@ Substitution Description creation date if photo is not modified. See https://strftime.org/ for help on strftime templates. + {today.date} Current date in iso format, e.g. '2020-03-22' {today.year} 4-digit year of current date {today.yy} 2-digit year of current date {today.mm} 2-digit month of the current date (zero padded) + {today.month} Month name in user's locale of the current date + {today.mon} Month abbreviation in the user's locale of the current date + {today.dd} 2-digit day of the month (zero padded) of current date + {today.dow} Day of week in user's locale of the current date + {today.doy} 3-digit day of year (e.g Julian day) of current date, starting from 1 (zero padded) + {today.hour} 2-digit hour of the current date {today.min} 2-digit minute of the current date {today.sec} 2-digit second of the current date @@ -934,51 +1055,70 @@ Substitution Description no template will return null value. See https://strftime.org/ for help on strftime templates. + {place.name} Place name from the photo's reverse geolocation data, as displayed in Photos + {place.country_code} The ISO country code from the photo's reverse geolocation data + {place.name.country} Country name from the photo's reverse geolocation data + {place.name.state_province} State or province name from the photo's reverse geolocation data + {place.name.city} City or locality name from the photo's reverse geolocation data + {place.name.area_of_interest} Area of interest name (e.g. landmark or public place) from the photo's reverse geolocation data + {place.address} Postal address from the photo's reverse geolocation data, e.g. '2007 18th St NW, Washington, DC 20009, United States' + {place.address.street} Street part of the postal address, e.g. '2007 18th St NW' + {place.address.city} City part of the postal address, e.g. 'Washington' + {place.address.state_province} State/province part of the postal address, e.g. 'DC' + {place.address.postal_code} Postal code part of the postal address, e.g. '20009' + {place.address.country} Country name of the postal address, e.g. 'United States' + {place.address.country_code} ISO country code of the postal address, e.g. 'US' + {searchinfo.season} Season of the year associated with a photo, e.g. 'Summer'; (Photos 5+ only, applied automatically by Photos' image categorization algorithms). + {exif.camera_make} Camera make from original photo's EXIF information as imported by Photos, e.g. 'Apple' + {exif.camera_model} Camera model from original photo's EXIF information as imported by Photos, e.g. 'iPhone 6s' + {exif.lens_model} Lens model from original photo's EXIF information as imported by Photos, e.g. 'iPhone 6s back camera 4.15mm f/2.2' + {uuid} Photo's internal universally unique identifier (UUID) for the photo, a 36-character string unique to the photo, e.g. '128FB4C6-0B16-4E7D-9108-FB2E90DA1546' + {comma} A comma: ',' {semicolon} A semicolon: ';' {pipe} A vertical pipe: '|' @@ -1001,13 +1141,16 @@ Substitution Description {folder_album} Folder path + album photo is contained in. e.g. 'Folder/Subfolder/Album' or just 'Album' if no enclosing folder + {keyword} Keyword(s) assigned to photo {person} Person(s) / face(s) in a photo {label} Image categorization label associated with a photo (Photos 5+ only) + {label_normalized} All lower case version of 'label' (Photos 5+ only) {comment} Comment(s) on shared Photos; format is 'Person name: comment text' (Photos 5+ only) + {exiftool} Format: '{exiftool:GROUP:TAGNAME}'; use exiftool (https://exiftool.org) to extract metadata, in form GROUP:TAGNAME, from image. E.g. @@ -1017,21 +1160,26 @@ Substitution Description names. You must specify group (e.g. EXIF, IPTC, etc) as used in `exiftool -G`. exiftool must be installed in the path to use this template. + {searchinfo.holiday} Holiday names associated with a photo, e.g. 'Christmas Day'; (Photos 5+ only, applied automatically by Photos' image categorization algorithms). + {searchinfo.activity} Activities associated with a photo, e.g. 'Sporting Event'; (Photos 5+ only, applied automatically by Photos' image categorization algorithms). + {searchinfo.venue} Venues associated with a photo, e.g. name of restaurant; (Photos 5+ only, applied automatically by Photos' image categorization algorithms). + {searchinfo.venue_type} Venue types associated with a photo, e.g. 'Restaurant'; (Photos 5+ only, applied automatically by Photos' image categorization algorithms). + ``` diff --git a/docs/.buildinfo b/docs/.buildinfo index 2d4f91df..90fc9c14 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: d0470550c1fa9feae481cebbbbc126af +config: 945c248ccd49635b8e71dede61ad6da7 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_modules/index.html b/docs/_modules/index.html index 2ac119e5..4c71d856 100644 --- a/docs/_modules/index.html +++ b/docs/_modules/index.html @@ -5,7 +5,7 @@ - Overview: module code — osxphotos 0.41.0 documentation + Overview: module code — osxphotos 0.41.2 documentation @@ -93,7 +93,7 @@ ©2021, Rhet Turnbull. | - Powered by Sphinx 3.4.3 + Powered by Sphinx 3.5.2 & Alabaster 0.7.12 diff --git a/docs/_modules/osxphotos/photoinfo/_photoinfo_export.html b/docs/_modules/osxphotos/photoinfo/_photoinfo_export.html index b82999e3..873c6dcb 100644 --- a/docs/_modules/osxphotos/photoinfo/_photoinfo_export.html +++ b/docs/_modules/osxphotos/photoinfo/_photoinfo_export.html @@ -5,7 +5,7 @@ - osxphotos.photoinfo._photoinfo_export — osxphotos 0.41.0 documentation + osxphotos.photoinfo._photoinfo_export — osxphotos 0.41.2 documentation @@ -508,6 +508,9 @@ merge_exif_keywords=False, merge_exif_persons=False, jpeg_ext=None, + persons=True, + location=True, + replace_keywords=False, ): """export photo, like export but with update and dry_run options dest: must be valid destination path or exception raised @@ -560,6 +563,9 @@ merge_exif_keywords: boolean; if True, merged keywords found in file's exif data (requires exiftool) merge_exif_persons: boolean; if True, merged persons found in file's exif data (requires exiftool) 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 Returns: ExportResults class ExportResults has attributes: @@ -974,6 +980,9 @@ merge_exif_keywords=merge_exif_keywords, merge_exif_persons=merge_exif_persons, filename=dest.name, + persons=persons, + location=location, + replace_keywords=replace_keywords, ) sidecars.append( ( @@ -997,6 +1006,9 @@ merge_exif_keywords=merge_exif_keywords, merge_exif_persons=merge_exif_persons, filename=dest.name, + persons=persons, + location=location, + replace_keywords=replace_keywords, ) sidecars.append( ( @@ -1016,6 +1028,9 @@ keyword_template=keyword_template, description_template=description_template, extension=dest.suffix[1:] if dest.suffix else None, + persons=persons, + location=location, + replace_keywords=replace_keywords, ) sidecars.append( ( @@ -1083,6 +1098,9 @@ ignore_date_modified=ignore_date_modified, merge_exif_keywords=merge_exif_keywords, merge_exif_persons=merge_exif_persons, + persons=persons, + location=location, + replace_keywords=replace_keywords, ) )[0] if old_data != current_data: @@ -1103,6 +1121,9 @@ flags=exiftool_flags, merge_exif_keywords=merge_exif_keywords, merge_exif_persons=merge_exif_persons, + persons=persons, + location=location, + replace_keywords=replace_keywords, ) if warning_: all_results.exiftool_warning.append((exported_file, warning_)) @@ -1120,6 +1141,9 @@ ignore_date_modified=ignore_date_modified, merge_exif_keywords=merge_exif_keywords, merge_exif_persons=merge_exif_persons, + persons=persons, + location=location, + replace_keywords=replace_keywords, ), ) export_db.set_stat_exif_for_file( @@ -1142,6 +1166,9 @@ flags=exiftool_flags, merge_exif_keywords=merge_exif_keywords, merge_exif_persons=merge_exif_persons, + persons=persons, + location=location, + replace_keywords=replace_keywords, ) if warning_: all_results.exiftool_warning.append((exported_file, warning_)) @@ -1159,6 +1186,9 @@ ignore_date_modified=ignore_date_modified, merge_exif_keywords=merge_exif_keywords, merge_exif_persons=merge_exif_persons, + persons=persons, + location=location, + replace_keywords=replace_keywords, ), ) export_db.set_stat_exif_for_file( @@ -1378,6 +1408,9 @@ flags=None, merge_exif_keywords=False, merge_exif_persons=False, + persons=True, + location=True, + replace_keywords=False, ): """write exif data to image file at filepath @@ -1388,6 +1421,9 @@ keyword_template: (list of strings); list of template strings to render as keywords ignore_date_modified: if True, sets EXIF:ModifyDate to EXIF:DateTimeOriginal even if date_modified is set flags: optional list of exiftool flags to prepend to exiftool command when writing metadata (e.g. -m or -F) + persons: if True, write person data to metadata + location: if True, write location data to metadata + replace_keywords: if True, keyword_template replaces any keywords, otherwise it's additive Returns: (warning, error) of warning and error strings if exiftool produces warnings or errors @@ -1402,6 +1438,9 @@ ignore_date_modified=ignore_date_modified, merge_exif_keywords=merge_exif_keywords, merge_exif_persons=merge_exif_persons, + persons=persons, + location=location, + replace_keywords=replace_keywords, ) with ExifTool(filepath, flags=flags, exiftool=self._db._exiftool_path) as exiftool: @@ -1424,6 +1463,9 @@ merge_exif_keywords=False, merge_exif_persons=False, filename=None, + persons=True, + location=True, + replace_keywords=False, ): """Return dict of EXIF details for building exiftool JSON sidecar or sending commands to ExifTool. Does not include all the EXIF fields as those are likely already in the image. @@ -1437,6 +1479,9 @@ ignore_date_modified: if True, sets EXIF:ModifyDate to EXIF:DateTimeOriginal even if date_modified is set merge_exif_keywords: merge keywords in the file's exif metadata (requires exiftool) merge_exif_persons: merge persons in the file's exif metadata (requires exiftool) + persons: if True, include person data + location: if True, include location data + replace_keywords: if True, keyword_template replaces any keywords, otherwise it's additive Returns: dict with exiftool tags / values @@ -1444,8 +1489,10 @@ EXIF:ImageDescription (may include template) XMP:Description (may include template) XMP:Title + IPTC:ObjectName XMP:TagsList (may include album name, person name, or template) IPTC:Keywords (may include album name, person name, or template) + IPTC:Caption-Abstract XMP:Subject (set to keywords + persons) XMP:PersonInImage EXIF:GPSLatitudeRef, EXIF:GPSLongitudeRef @@ -1461,6 +1508,9 @@ QuickTime:ModifyDate (UTC) QuickTime:GPSCoordinates UserData:GPSCoordinates + + Reference: + https://iptc.org/std/photometadata/specification/IPTC-PhotoMetadata-201610_1.pdf """ exif = ( @@ -1480,30 +1530,34 @@ description = " ".join(rendered) if rendered else "" exif["EXIF:ImageDescription"] = description exif["XMP:Description"] = description + exif["IPTC:Caption-Abstract"] = description elif self.description: exif["EXIF:ImageDescription"] = self.description exif["XMP:Description"] = self.description + exif["IPTC:Caption-Abstract"] = self.description if self.title: exif["XMP:Title"] = self.title + exif["IPTC:ObjectName"] = self.title keyword_list = [] if merge_exif_keywords: keyword_list.extend(self._get_exif_keywords()) - if self.keywords: + if self.keywords and not replace_keywords: keyword_list.extend(self.keywords) person_list = [] - if merge_exif_persons: - person_list.extend(self._get_exif_persons()) + if persons: + if merge_exif_persons: + person_list.extend(self._get_exif_persons()) - if self.persons: - # filter out _UNKNOWN_PERSON - person_list.extend([p for p in self.persons if p != _UNKNOWN_PERSON]) + if self.persons: + # filter out _UNKNOWN_PERSON + person_list.extend([p for p in self.persons if p != _UNKNOWN_PERSON]) - if use_persons_as_keywords and person_list: - keyword_list.extend(person_list) + if use_persons_as_keywords and person_list: + keyword_list.extend(person_list) if use_albums_as_keywords and self.albums: keyword_list.extend(self.albums) @@ -1547,25 +1601,26 @@ exif["XMP:Subject"] = keyword_list.copy() exif["XMP:TagsList"] = keyword_list.copy() - if person_list: + if persons and person_list: person_list = sorted(list(set(person_list))) exif["XMP:PersonInImage"] = person_list.copy() # if self.favorite(): # exif["Rating"] = 5 - (lat, lon) = self.location - if lat is not None and lon is not None: - if self.isphoto: - exif["EXIF:GPSLatitude"] = lat - exif["EXIF:GPSLongitude"] = lon - lat_ref = "N" if lat >= 0 else "S" - lon_ref = "E" if lon >= 0 else "W" - exif["EXIF:GPSLatitudeRef"] = lat_ref - exif["EXIF:GPSLongitudeRef"] = lon_ref - elif self.ismovie: - exif["Keys:GPSCoordinates"] = f"{lat} {lon}" - exif["UserData:GPSCoordinates"] = f"{lat} {lon}" + if location: + (lat, lon) = self.location + if lat is not None and lon is not None: + if self.isphoto: + exif["EXIF:GPSLatitude"] = lat + exif["EXIF:GPSLongitude"] = lon + lat_ref = "N" if lat >= 0 else "S" + lon_ref = "E" if lon >= 0 else "W" + exif["EXIF:GPSLatitudeRef"] = lat_ref + exif["EXIF:GPSLongitudeRef"] = lon_ref + elif self.ismovie: + exif["Keys:GPSCoordinates"] = f"{lat} {lon}" + exif["UserData:GPSCoordinates"] = f"{lat} {lon}" # process date/time and timezone offset # Photos exports the following fields and sets modify date to creation date @@ -1624,6 +1679,13 @@ exif["QuickTime:ModifyDate"] = datetime_tz_to_utc( self.date_modified ).strftime("%Y:%m:%d %H:%M:%S") + + # remove any new lines in any fields + for field, val in exif.items(): + if type(val) == str: + exif[field] = val.replace("\n", " ") + elif type(val) == list: + exif[field] = [v.replace("\n", " ") for v in val] return exif @@ -1673,6 +1735,9 @@ merge_exif_keywords=False, merge_exif_persons=False, filename=None, + persons=True, + location=True, + replace_keywords=False, ): """Return dict of EXIF details for building exiftool JSON sidecar or sending commands to ExifTool. Does not include all the EXIF fields as those are likely already in the image. @@ -1687,13 +1752,18 @@ merge_exif_keywords: boolean; if True, merged keywords found in file's exif data (requires exiftool) merge_exif_persons: boolean; if True, merged persons found in file's exif data (requires exiftool) filename: filename of the destination image file for including in exiftool signature in JSON sidecar + persons: if True, include person data + location: if True, include location data + replace_keywords: if True, keyword_template replaces any keywords, otherwise it's additive Returns: dict with exiftool tags / values Exports the following: EXIF:ImageDescription XMP:Description (may include template) + IPTC:CaptionAbstract XMP:Title + IPTC:ObjectName XMP:TagsList IPTC:Keywords (may include album name, person name, or template) XMP:Subject (set to keywords + person) @@ -1721,6 +1791,9 @@ merge_exif_keywords=merge_exif_keywords, merge_exif_persons=merge_exif_persons, filename=filename, + persons=persons, + location=location, + replace_keywords=replace_keywords, ) if not tag_groups: @@ -1743,6 +1816,9 @@ extension=None, merge_exif_keywords=False, merge_exif_persons=False, + persons=True, + location=True, + replace_keywords=False, ): """returns string for XMP sidecar use_albums_as_keywords: treat album names as keywords @@ -1752,6 +1828,9 @@ extension: which extension to use for SidecarForExtension property merge_exif_keywords: boolean; if True, merged keywords found in file's exif data (requires exiftool) merge_exif_persons: boolean; if True, merged persons found in file's exif data (requires exiftool) + persons: if True, include person data + location: if True, include location data + replace_keywords: if True, keyword_template replaces any keywords, otherwise it's additive """ xmp_template_file = ( @@ -1775,22 +1854,23 @@ if merge_exif_keywords: keyword_list.extend(self._get_exif_keywords()) - if self.keywords: + if self.keywords and not replace_keywords: keyword_list.extend(self.keywords) # TODO: keyword handling in this and _exiftool_json_sidecar is # good candidate for pulling out in a function person_list = [] - if merge_exif_persons: - person_list.extend(self._get_exif_persons()) + if persons: + if merge_exif_persons: + person_list.extend(self._get_exif_persons()) - if self.persons: - # filter out _UNKNOWN_PERSON - person_list.extend([p for p in self.persons if p != _UNKNOWN_PERSON]) + if self.persons: + # filter out _UNKNOWN_PERSON + person_list.extend([p for p in self.persons if p != _UNKNOWN_PERSON]) - if use_persons_as_keywords and person_list: - keyword_list.extend(person_list) + if use_persons_as_keywords and person_list: + keyword_list.extend(person_list) if use_albums_as_keywords and self.albums: keyword_list.extend(self.albums) @@ -1820,11 +1900,14 @@ # sorted mainly to make testing the XMP file easier if keyword_list: keyword_list = sorted(list(set(keyword_list))) - if person_list: + if persons and person_list: person_list = sorted(list(set(person_list))) subject_list = keyword_list + if location: + latlon = self.location + xmp_str = xmp_template.render( photo=self, description=description, @@ -1832,6 +1915,7 @@ persons=person_list, subjects=subject_list, extension=extension, + location=latlon, version=__version__, ) @@ -1912,7 +1996,7 @@ ©2021, Rhet Turnbull. | - Powered by Sphinx 3.4.3 + Powered by Sphinx 3.5.2 & Alabaster 0.7.12 diff --git a/docs/_static/doctools.js b/docs/_static/doctools.js index 144884ea..61ac9d26 100644 --- a/docs/_static/doctools.js +++ b/docs/_static/doctools.js @@ -29,9 +29,14 @@ if (!window.console || !console.firebug) { /** * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL */ jQuery.urldecode = function(x) { - return decodeURIComponent(x).replace(/\+/g, ' '); + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); }; /** diff --git a/docs/_static/documentation_options.js b/docs/_static/documentation_options.js index 9f32676c..a77a8322 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.41.0', + VERSION: '0.41.2', LANGUAGE: 'None', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/docs/_static/language_data.js b/docs/_static/language_data.js index 0e7dc7e9..863704b3 100644 --- a/docs/_static/language_data.js +++ b/docs/_static/language_data.js @@ -13,7 +13,8 @@ var stopwords = ["a","and","are","as","at","be","but","by","for","if","in","into","is","it","near","no","not","of","on","or","such","that","the","their","then","there","these","they","this","to","was","will","with"]; -/* Non-minified version JS is _stemmer.js if file is provided */ +/* Non-minified version is copied as a separate JS file, is available */ + /** * Porter Stemmer */ @@ -199,7 +200,6 @@ var Stemmer = function() { - var splitChars = (function() { var result = {}; var singles = [96, 180, 187, 191, 215, 247, 749, 885, 903, 907, 909, 930, 1014, 1648, diff --git a/docs/_static/pygments.css b/docs/_static/pygments.css index 540bf58b..87f8bd12 100644 --- a/docs/_static/pygments.css +++ b/docs/_static/pygments.css @@ -1,7 +1,7 @@ pre { line-height: 125%; } -td.linenos pre { color: #000000; background-color: #f0f0f0; padding-left: 5px; padding-right: 5px; } -span.linenos { color: #000000; background-color: #f0f0f0; padding-left: 5px; padding-right: 5px; } -td.linenos pre.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } .highlight .hll { background-color: #ffffcc } .highlight { background: #f8f8f8; } diff --git a/docs/_static/searchtools.js b/docs/_static/searchtools.js index 6fc9e7f3..1a90152e 100644 --- a/docs/_static/searchtools.js +++ b/docs/_static/searchtools.js @@ -248,7 +248,7 @@ var Search = { // results left, load the summary and display it if (results.length) { var item = results.pop(); - var listItem = $('
  • '); + var listItem = $('
  • '); var requestUrl = ""; var linkUrl = ""; if (DOCUMENTATION_OPTIONS.BUILDER === 'dirhtml') { @@ -273,9 +273,9 @@ var Search = { if (item[3]) { listItem.append($(' (' + item[3] + ')')); Search.output.append(listItem); - listItem.slideDown(5, function() { + setTimeout(function() { displayNextItem(); - }); + }, 5); } else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) { $.ajax({url: requestUrl, dataType: "text", @@ -285,16 +285,16 @@ var Search = { listItem.append(Search.makeSearchSummary(data, searchterms, hlterms)); } Search.output.append(listItem); - listItem.slideDown(5, function() { + setTimeout(function() { displayNextItem(); - }); + }, 5); }}); } else { // no source available, just display title Search.output.append(listItem); - listItem.slideDown(5, function() { + setTimeout(function() { displayNextItem(); - }); + }, 5); } } // search finished, update title and status message @@ -379,6 +379,13 @@ var Search = { return results; }, + /** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions + */ + escapeRegExp : function(string) { + return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string + }, + /** * search for full-text terms in the index */ @@ -402,13 +409,14 @@ var Search = { ]; // add support for partial matches if (word.length > 2) { + var word_regex = this.escapeRegExp(word); for (var w in terms) { - if (w.match(word) && !terms[word]) { + if (w.match(word_regex) && !terms[word]) { _o.push({files: terms[w], score: Scorer.partialTerm}) } } for (var w in titleterms) { - if (w.match(word) && !titleterms[word]) { + if (w.match(word_regex) && !titleterms[word]) { _o.push({files: titleterms[w], score: Scorer.partialTitle}) } } diff --git a/docs/_static/underscore-1.12.0.js b/docs/_static/underscore-1.12.0.js new file mode 100644 index 00000000..3af6352e --- /dev/null +++ b/docs/_static/underscore-1.12.0.js @@ -0,0 +1,2027 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define('underscore', factory) : + (global = global || self, (function () { + var current = global._; + var exports = global._ = factory(); + exports.noConflict = function () { global._ = current; return exports; }; + }())); +}(this, (function () { + // Underscore.js 1.12.0 + // https://underscorejs.org + // (c) 2009-2020 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + // Underscore may be freely distributed under the MIT license. + + // Current version. + var VERSION = '1.12.0'; + + // 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(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(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(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(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 _(obj) { + if (obj instanceof _) return obj; + if (!(this instanceof _)) return new _(obj); + this._wrapped = obj; + } + + _.VERSION = VERSION; + + // Extracts the result from a wrapped and chained object. + _.prototype.value = function() { + return this._wrapped; + }; + + // Provide unwrapping proxies for some methods used in engine operations + // such as arithmetic and JSON stringification. + _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; + + _.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 _) a = a._wrapped; + if (b instanceof _) 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(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(path) { + return isArray(path) ? path : [path]; + } + _.toPath = toPath; + + // Internal wrapper for `_.toPath` to enable minification. + // Similar to `cb` for `_.iteratee`. + function toPath$1(path) { + return _.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$1(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$1(obj, path) { + path = toPath$1(path); + var length = path.length; + for (var i = 0; i < length; i++) { + var key = path[i]; + if (!has(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$1(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); + } + _.iteratee = iteratee; + + // The function we call internally to generate a callback. It invokes + // `_.iteratee` if overridden, otherwise `baseIteratee`. + function cb(value, context, argCount) { + if (_.iteratee !== iteratee) return _.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 = _.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]; + } + + // 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, _.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"; + + // If a variable is not specified, place data values in local scope. + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; + + 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(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + + var template = function(data) { + return render.call(this, data, _); + }; + + // Provide the compiled source as a convenience for precompilation. + var argument = settings.variable || 'obj'; + 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$1(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 = _(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 = _; + + // 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(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(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(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(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); + + // 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$1(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(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(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(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(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$1(array, depth) { + return flatten(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(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(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 ? _(obj).chain() : obj; + } + + // Add your own custom functions to the Underscore object. + function mixin(obj) { + each(functions(obj), function(name) { + var func = _[name] = obj[name]; + _.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return chainResult(this, func.apply(_, args)); + }; + }); + return _; + } + + // Add all mutator `Array` functions to the wrapper. + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = ArrayProto[name]; + _.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]; + _.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$1, + mapObject: mapObject, + identity: identity, + constant: constant, + noop: noop, + toPath: toPath, + 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$1, + 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': _ + }; + + // Default Export + + // Add all of the Underscore functions to the wrapper object. + var _$1 = mixin(allExports); + // Legacy Node.js API. + _$1._ = _$1; + + return _$1; + +}))); +//# sourceMappingURL=underscore.js.map diff --git a/docs/_static/underscore.js b/docs/_static/underscore.js index 5b55f32b..166240ef 100644 --- a/docs/_static/underscore.js +++ b/docs/_static/underscore.js @@ -1,31 +1,6 @@ -// Underscore.js 1.3.1 -// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. -// Underscore is freely distributable under the MIT license. -// Portions of Underscore are inspired or borrowed from Prototype, -// Oliver Steele's Functional, and John Resig's Micro-Templating. -// For all details and documentation: -// http://documentcloud.github.com/underscore -(function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== -c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c, -h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each= -b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;a== -null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect= -function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e= -e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= -function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a, -c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}}; -b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments, -1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)}; -b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"}; -b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a), -function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+ -u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]= -function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain= -true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); +!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 +// https://underscorejs.org +// (c) 2009-2020 Jeremy Ashkenas, 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 - osxphotos command line interface (CLI) — osxphotos 0.41.0 documentation + osxphotos command line interface (CLI) — osxphotos 0.41.2 documentation @@ -42,19 +42,19 @@

    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.

    @@ -74,20 +74,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.

    Arguments

    -PHOTOS_LIBRARY
    +PHOTOS_LIBRARY

    Optional argument(s)

    @@ -101,32 +101,32 @@

    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)

    @@ -149,644 +149,650 @@ 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)

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

    Search for photos with UUID(s).

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

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

    +--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>
    +--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 start item date, e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601).

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

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

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

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

    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.

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

    +--export-as-hardlink

    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)

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

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

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

    +–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’.

    Options
    -

    xmp|json|exiftool

    +

    xmp | json | exiftool

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

    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 Templating System below.

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

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

    jpeg|jpg|JPEG|JPG

    +

    jpeg | jpg | JPEG | JPG

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

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

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

    @@ -800,7 +806,7 @@ to modify this behavior.

    Arguments

    -TOPIC
    +TOPIC

    Optional argument

    @@ -814,20 +820,20 @@ 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)

    @@ -841,20 +847,20 @@ 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)

    @@ -868,20 +874,20 @@ 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)

    @@ -895,7 +901,7 @@ to modify this behavior.

    Options

    ---json
    +--json

    Print output in JSON format.

    @@ -909,20 +915,20 @@ 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)

    @@ -936,20 +942,20 @@ 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)

    @@ -965,386 +971,386 @@ 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)

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

    Search for photos with UUID(s).

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

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

    +--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>
    +--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 start item date, e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601).

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

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

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

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

    Arguments

    -PHOTOS_LIBRARY
    +PHOTOS_LIBRARY

    Optional argument(s)

    @@ -1426,7 +1432,7 @@ if more than one option is provided, they are treated as “AND” ©2021, Rhet Turnbull. | - Powered by Sphinx 3.4.3 + Powered by Sphinx 3.5.2 & Alabaster 0.7.12 | diff --git a/docs/genindex.html b/docs/genindex.html index dcb38c2f..6e1d8129 100644 --- a/docs/genindex.html +++ b/docs/genindex.html @@ -5,7 +5,7 @@ - Index — osxphotos 0.41.0 documentation + Index — osxphotos 0.41.2 documentation @@ -776,6 +776,13 @@
  • osxphotos-export command line option
  • osxphotos-query command line option +
  • + +
  • + --replace-keywords + +
  • @@ -1017,6 +1024,8 @@
    -
  • albums_as_dict() (osxphotos.PhotosDB property) -