diff --git a/osxphotos/cli/common.py b/osxphotos/cli/common.py index 6cc59c12..892fa98a 100644 --- a/osxphotos/cli/common.py +++ b/osxphotos/cli/common.py @@ -380,6 +380,31 @@ def QUERY_OPTIONS(f): multiple=True, type=int, ), + o( + "--added-before", + metavar="DATE", + help="Search for items added to the library before a specific date/time, " + "e.g. --added-before e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601 with/without timezone).", + type=DateTimeISO8601(), + ), + o( + "--added-after", + metavar="DATE", + help="Search for items added to the libray after a specific date/time, " + "e.g. --added-after e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601 with/without timezone).", + type=DateTimeISO8601(), + ), + o( + "--added-in-last", + metavar="TIME_DELTA", + help="Search for items added to the library in the last TIME_DELTA, " + "where TIME_DELTA is a string like " + "'12 hrs', '1 day', '1d', '1 week', '2weeks', '1 month', '1 year'. " + "for example, `--added-in-last 7d` and `--added-in-last '1 week'` are equivalent. " + "months are assumed to be 30 days and years are assumed to be 365 days. " + "Common English abbreviations are accepted, e.g. d, day, days or m, min, minutes.", + type=TimeOffset(), + ), o("--has-comment", is_flag=True, help="Search for photos that have comments."), o("--no-comment", is_flag=True, help="Search for photos with no comments."), o("--has-likes", is_flag=True, help="Search for photos that have likes."), @@ -569,4 +594,3 @@ def check_version(): "to suppress this message and prevent osxphotos from checking for latest version.", err=True, ) - diff --git a/osxphotos/cli/export.py b/osxphotos/cli/export.py index eeef3c5f..99f89f56 100644 --- a/osxphotos/cli/export.py +++ b/osxphotos/cli/export.py @@ -672,145 +672,148 @@ def export( cli_obj, db, photos_library, - keyword, - person, - album, - folder, - uuid, - name, - uuid_from_file, - title, - no_title, - description, - no_description, - uti, - ignore_case, - edited, - external_edit, - favorite, - not_favorite, - hidden, - not_hidden, - shared, - not_shared, - from_date, - to_date, - from_time, - to_time, - year, - verbose, - timestamp, - no_progress, - missing, - update, - force_update, - ignore_signature, - only_new, - dry_run, - export_as_hardlink, - touch_file, - overwrite, - retry, - export_by_date, - skip_edited, - skip_original_if_edited, - skip_bursts, - skip_live, - skip_raw, - skip_uuid, - skip_uuid_from_file, - person_keyword, + add_exported_to_album, + add_missing_to_album, + add_skipped_to_album, + added_after, + added_before, + added_in_last, album_keyword, - keyword_template, - replace_keywords, - description_template, - finder_tag_template, - finder_tag_keywords, - xattr_template, - current_name, - convert_to_jpeg, - jpeg_quality, - sidecar, - sidecar_drop_ext, - only_photos, - only_movies, + album, + beta, burst, - not_burst, - live, - not_live, - download_missing, + cleanup, + config_only, + convert_to_jpeg, + current_name, + deleted_only, + deleted, + description_template, + description, dest, - exiftool, - exiftool_path, - exiftool_option, + directory, + download_missing, + dry_run, + duplicate, + edited_suffix, + edited, + exif, exiftool_merge_keywords, exiftool_merge_persons, - ignore_date_modified, - portrait, - not_portrait, - screenshot, - not_screenshot, - slow_mo, - not_slow_mo, - time_lapse, - not_time_lapse, - hdr, - not_hdr, - selfie, - not_selfie, - panorama, - not_panorama, - has_raw, - directory, - filename_template, - jpeg_ext, - strip, - edited_suffix, - original_suffix, - place, - no_place, - location, - no_location, - has_comment, - no_comment, - has_likes, - no_likes, - label, - deleted, - deleted_only, - use_photos_export, - use_photokit, - report, - cleanup, - add_exported_to_album, - add_skipped_to_album, - add_missing_to_album, + exiftool_option, + exiftool_path, + exiftool, + export_as_hardlink, + export_by_date, exportdb, - ramdb, - tmpdir, - load_config, - save_config, - config_only, - is_reference, - beta, + external_edit, + favorite, + filename_template, + finder_tag_keywords, + finder_tag_template, + folder, + force_update, + from_date, + from_time, + has_comment, + has_likes, + has_raw, + hdr, + hidden, + ignore_case, + ignore_date_modified, + ignore_signature, in_album, - not_in_album, - min_size, + is_reference, + jpeg_ext, + jpeg_quality, + keyword_template, + keyword, + label, + live, + load_config, + location, max_size, - regex, - selected, - exif, - query_eval, - query_function, - duplicate, + min_size, + missing, + name, + no_comment, + no_description, + no_likes, + no_location, + no_place, + no_progress, + no_title, + not_burst, + not_favorite, + not_hdr, + not_hidden, + not_in_album, + not_live, + not_panorama, + not_portrait, + not_screenshot, + not_selfie, + not_shared, + not_slow_mo, + not_time_lapse, + only_movies, + only_new, + only_photos, + original_suffix, + overwrite, + panorama, + person_keyword, + person, + place, + portrait, post_command, post_function, - preview, - preview_suffix, preview_if_missing, - profile, + preview_suffix, + preview, profile_sort, + profile, + query_eval, + query_function, + ramdb, + regex, + replace_keywords, + report, + retry, + save_config, + screenshot, + selected, + selfie, + shared, + sidecar_drop_ext, + sidecar, + skip_bursts, + skip_edited, + skip_live, + skip_original_if_edited, + skip_raw, + skip_uuid_from_file, + skip_uuid, + slow_mo, + strip, theme, + time_lapse, + timestamp, + title, + tmpdir, + to_date, + to_time, + touch_file, + update, + use_photokit, + use_photos_export, + uti, + uuid_from_file, + uuid, + verbose, + xattr_template, + year, debug, # debug, watch, breakpoint handled in cli/__init__.py watch, breakpoint, @@ -886,6 +889,9 @@ def export( # re-set the local vars to the corresponding config value # this isn't elegant but avoids having to rewrite this function to use cfg.varname for every parameter + added_after = cfg.added_after + added_before = cfg.added_before + added_in_last = cfg.added_in_last add_exported_to_album = cfg.add_exported_to_album add_missing_to_album = cfg.add_missing_to_album add_skipped_to_album = cfg.add_skipped_to_album @@ -1038,41 +1044,41 @@ def export( # validate options exclusive_options = [ - ("favorite", "not_favorite"), - ("hidden", "not_hidden"), - ("title", "no_title"), - ("description", "no_description"), - ("only_photos", "only_movies"), ("burst", "not_burst"), - ("live", "not_live"), - ("portrait", "not_portrait"), - ("screenshot", "not_screenshot"), - ("slow_mo", "not_slow_mo"), - ("time_lapse", "not_time_lapse"), - ("hdr", "not_hdr"), - ("selfie", "not_selfie"), - ("panorama", "not_panorama"), - ("export_by_date", "directory"), - ("export_as_hardlink", "exiftool"), - ("place", "no_place"), ("deleted", "deleted_only"), - ("skip_edited", "skip_original_if_edited"), + ("description", "no_description"), ("export_as_hardlink", "convert_to_jpeg"), ("export_as_hardlink", "download_missing"), - ("shared", "not_shared"), + ("export_as_hardlink", "exiftool"), + ("export_by_date", "directory"), + ("favorite", "not_favorite"), ("has_comment", "no_comment"), ("has_likes", "no_likes"), + ("hdr", "not_hdr"), + ("hidden", "not_hidden"), ("in_album", "not_in_album"), + ("live", "not_live"), ("location", "no_location"), + ("only_photos", "only_movies"), + ("panorama", "not_panorama"), + ("place", "no_place"), + ("portrait", "not_portrait"), + ("screenshot", "not_screenshot"), + ("selfie", "not_selfie"), + ("shared", "not_shared"), + ("skip_edited", "skip_original_if_edited"), + ("slow_mo", "not_slow_mo"), + ("time_lapse", "not_time_lapse"), + ("title", "no_title"), ] dependent_options = [ - ("missing", ("download_missing", "use_photos_export")), - ("jpeg_quality", ("convert_to_jpeg")), - ("ignore_signature", ("update", "force_update")), - ("only_new", ("update", "force_update")), - ("exiftool_option", ("exiftool")), ("exiftool_merge_keywords", ("exiftool", "sidecar")), ("exiftool_merge_persons", ("exiftool", "sidecar")), + ("exiftool_option", ("exiftool")), + ("ignore_signature", ("update", "force_update")), + ("jpeg_quality", ("convert_to_jpeg")), + ("missing", ("download_missing", "use_photos_export")), + ("only_new", ("update", "force_update")), ] try: cfg.validate(exclusive=exclusive_options, dependent=dependent_options, cli=True) @@ -1267,83 +1273,86 @@ def export( photosdb._beta = beta query_options = QueryOptions( - keyword=keyword, - person=person, + added_after=added_after, + added_before=added_before, + added_in_last=added_in_last, album=album, - folder=folder, - uuid=uuid, - title=title, - no_title=no_title, + burst_photos=export_bursts, + burst=burst, + cloudasset=False, + deleted_only=deleted_only, + deleted=deleted, description=description, - no_description=no_description, - ignore_case=ignore_case, + duplicate=duplicate, edited=edited, + exif=exif, external_edit=external_edit, favorite=favorite, - not_favorite=not_favorite, - hidden=hidden, - not_hidden=not_hidden, - missing=missing, - not_missing=None, - shared=shared, - not_shared=not_shared, - photos=photos, - movies=movies, - uti=uti, - burst=burst, - not_burst=not_burst, - live=live, - not_live=not_live, - cloudasset=False, - not_cloudasset=False, - incloud=False, - not_incloud=False, + folder=folder, from_date=from_date, - to_date=to_date, from_time=from_time, - to_time=to_time, - year=year, - portrait=portrait, - not_portrait=not_portrait, - screenshot=screenshot, - not_screenshot=not_screenshot, - slow_mo=slow_mo, - not_slow_mo=not_slow_mo, - time_lapse=time_lapse, - not_time_lapse=not_time_lapse, - hdr=hdr, - not_hdr=not_hdr, - selfie=selfie, - not_selfie=not_selfie, - panorama=panorama, - not_panorama=not_panorama, - has_raw=has_raw, - place=place, - no_place=no_place, - location=location, - no_location=no_location, - label=label, - deleted=deleted, - deleted_only=deleted_only, + function=query_function, has_comment=has_comment, - no_comment=no_comment, has_likes=has_likes, - no_likes=no_likes, - is_reference=is_reference, + has_raw=has_raw, + hdr=hdr, + hidden=hidden, + ignore_case=ignore_case, in_album=in_album, - not_in_album=not_in_album, - burst_photos=export_bursts, + incloud=False, + is_reference=is_reference, + keyword=keyword, + label=label, + live=live, + location=location, + max_size=max_size, + min_size=min_size, # skip missing bursts if using --download-missing by itself as AppleScript otherwise causes errors missing_bursts=(download_missing and use_photokit) or not download_missing, + missing=missing, + movies=movies, name=name, - min_size=min_size, - max_size=max_size, - regex=regex, - selected=selected, - exif=exif, + no_comment=no_comment, + no_description=no_description, + no_likes=no_likes, + no_location=no_location, + no_place=no_place, + no_title=no_title, + not_burst=not_burst, + not_cloudasset=False, + not_favorite=not_favorite, + not_hdr=not_hdr, + not_hidden=not_hidden, + not_in_album=not_in_album, + not_incloud=False, + not_live=not_live, + not_missing=None, + not_panorama=not_panorama, + not_portrait=not_portrait, + not_screenshot=not_screenshot, + not_selfie=not_selfie, + not_shared=not_shared, + not_slow_mo=not_slow_mo, + not_time_lapse=not_time_lapse, + panorama=panorama, + person=person, + photos=photos, + place=place, + portrait=portrait, query_eval=query_eval, - function=query_function, - duplicate=duplicate, + regex=regex, + screenshot=screenshot, + selected=selected, + selfie=selfie, + shared=shared, + slow_mo=slow_mo, + time_lapse=time_lapse, + title=title, + to_date=to_date, + to_time=to_time, + uti=uti, + uuid=uuid, + year=year, ) try: diff --git a/osxphotos/cli/query.py b/osxphotos/cli/query.py index b9642b2d..65f5f096 100644 --- a/osxphotos/cli/query.py +++ b/osxphotos/cli/query.py @@ -73,83 +73,86 @@ def query( cli_obj, db, photos_library, - keyword, - person, + add_to_album, + added_after, + added_before, + added_in_last, album, - folder, - name, - uuid, - uuid_from_file, - title, - no_title, + burst, + cloudasset, + deleted_only, + deleted, description, - no_description, - ignore_case, - json_, + duplicate, edited, + exif, external_edit, favorite, - not_favorite, + folder, + from_date, + from_time, + has_comment, + has_likes, + has_raw, + hdr, hidden, - not_hidden, + ignore_case, + in_album, + incloud, + is_reference, + json_, + keyword, + label, + live, + location, + max_size, + min_size, missing, + name, + no_comment, + no_description, + no_likes, + no_location, + no_place, + no_title, + not_burst, + not_cloudasset, + not_favorite, + not_hdr, + not_hidden, + not_in_album, + not_incloud, + not_live, not_missing, - shared, + not_panorama, + not_portrait, + not_screenshot, + not_selfie, not_shared, + not_slow_mo, + not_time_lapse, only_movies, only_photos, - uti, - burst, - not_burst, - live, - not_live, - cloudasset, - not_cloudasset, - incloud, - not_incloud, - from_date, - to_date, - from_time, - to_time, - year, - portrait, - not_portrait, - screenshot, - not_screenshot, - slow_mo, - not_slow_mo, - time_lapse, - not_time_lapse, - hdr, - not_hdr, - selfie, - not_selfie, panorama, - not_panorama, - has_raw, + person, place, - no_place, - location, - no_location, - label, - deleted, - deleted_only, - has_comment, - no_comment, - has_likes, - no_likes, - is_reference, - in_album, - not_in_album, - duplicate, - min_size, - max_size, - regex, - selected, - exif, + portrait, query_eval, query_function, - add_to_album, + regex, + screenshot, + selected, + selfie, + shared, + slow_mo, + time_lapse, + title, + to_date, + to_time, + uti, + uuid_from_file, + uuid, + year, debug, # handled in cli/__init__.py ): """Query the Photos database using 1 or more search options; @@ -160,58 +163,61 @@ def query( # if no query terms, show help and return # sanity check input args nonexclusive = [ - keyword, - person, + added_after, + added_before, + added_in_last, album, - folder, - name, - uuid, - uuid_from_file, + duplicate, edited, + exif, external_edit, - uti, - has_raw, + folder, from_date, - to_date, from_time, - to_time, - year, - label, + has_raw, is_reference, + keyword, + label, + max_size, + min_size, + name, + person, query_eval, query_function, - min_size, - max_size, regex, selected, - exif, - duplicate, + to_date, + to_time, + uti, + uuid_from_file, + uuid, + year, ] exclusive = [ - (favorite, not_favorite), - (hidden, not_hidden), - (missing, not_missing), - (any(title), no_title), (any(description), no_description), - (only_photos, only_movies), - (burst, not_burst), - (live, not_live), - (cloudasset, not_cloudasset), - (incloud, not_incloud), - (portrait, not_portrait), - (screenshot, not_screenshot), - (slow_mo, not_slow_mo), - (time_lapse, not_time_lapse), - (hdr, not_hdr), - (selfie, not_selfie), - (panorama, not_panorama), (any(place), no_place), + (any(title), no_title), + (burst, not_burst), + (cloudasset, not_cloudasset), (deleted, deleted_only), - (shared, not_shared), + (favorite, not_favorite), (has_comment, no_comment), (has_likes, no_likes), + (hdr, not_hdr), + (hidden, not_hidden), (in_album, not_in_album), + (incloud, not_incloud), + (live, not_live), (location, no_location), + (missing, not_missing), + (only_photos, only_movies), + (panorama, not_panorama), + (portrait, not_portrait), + (screenshot, not_screenshot), + (selfie, not_selfie), + (shared, not_shared), + (slow_mo, not_slow_mo), + (time_lapse, not_time_lapse), ] # print help if no non-exclusive term or a double exclusive term is given if any(all(bb) for bb in exclusive) or not any( @@ -247,80 +253,83 @@ def query( photosdb = osxphotos.PhotosDB(dbfile=db) query_options = QueryOptions( - keyword=keyword, - person=person, + added_after=added_after, + added_before=added_before, + added_in_last=added_in_last, album=album, - folder=folder, - uuid=uuid, - title=title, - no_title=no_title, + burst=burst, + cloudasset=cloudasset, + deleted_only=deleted_only, + deleted=deleted, description=description, - no_description=no_description, - ignore_case=ignore_case, + duplicate=duplicate, edited=edited, + exif=exif, external_edit=external_edit, favorite=favorite, - not_favorite=not_favorite, - hidden=hidden, - not_hidden=not_hidden, - missing=missing, - not_missing=not_missing, - shared=shared, - not_shared=not_shared, - photos=photos, - movies=movies, - uti=uti, - burst=burst, - not_burst=not_burst, - live=live, - not_live=not_live, - cloudasset=cloudasset, - not_cloudasset=not_cloudasset, - incloud=incloud, - not_incloud=not_incloud, + folder=folder, from_date=from_date, - to_date=to_date, from_time=from_time, - to_time=to_time, - year=year, - portrait=portrait, - not_portrait=not_portrait, - screenshot=screenshot, - not_screenshot=not_screenshot, - slow_mo=slow_mo, - not_slow_mo=not_slow_mo, - time_lapse=time_lapse, - not_time_lapse=not_time_lapse, - hdr=hdr, - not_hdr=not_hdr, - selfie=selfie, - not_selfie=not_selfie, - panorama=panorama, - not_panorama=not_panorama, - has_raw=has_raw, - place=place, - no_place=no_place, - location=location, - no_location=no_location, - label=label, - deleted=deleted, - deleted_only=deleted_only, - has_comment=has_comment, - no_comment=no_comment, - has_likes=has_likes, - no_likes=no_likes, - is_reference=is_reference, - in_album=in_album, - not_in_album=not_in_album, - name=name, - min_size=min_size, - max_size=max_size, - query_eval=query_eval, function=query_function, + has_comment=has_comment, + has_likes=has_likes, + has_raw=has_raw, + hdr=hdr, + hidden=hidden, + ignore_case=ignore_case, + in_album=in_album, + incloud=incloud, + is_reference=is_reference, + keyword=keyword, + label=label, + live=live, + location=location, + max_size=max_size, + min_size=min_size, + missing=missing, + movies=movies, + name=name, + no_comment=no_comment, + no_description=no_description, + no_likes=no_likes, + no_location=no_location, + no_place=no_place, + no_title=no_title, + not_burst=not_burst, + not_cloudasset=not_cloudasset, + not_favorite=not_favorite, + not_hdr=not_hdr, + not_hidden=not_hidden, + not_in_album=not_in_album, + not_incloud=not_incloud, + not_live=not_live, + not_missing=not_missing, + not_panorama=not_panorama, + not_portrait=not_portrait, + not_screenshot=not_screenshot, + not_selfie=not_selfie, + not_shared=not_shared, + not_slow_mo=not_slow_mo, + not_time_lapse=not_time_lapse, + panorama=panorama, + person=person, + photos=photos, + place=place, + portrait=portrait, + query_eval=query_eval, regex=regex, + screenshot=screenshot, selected=selected, - exif=exif, - duplicate=duplicate, + selfie=selfie, + shared=shared, + slow_mo=slow_mo, + time_lapse=time_lapse, + title=title, + to_date=to_date, + to_time=to_time, + uti=uti, + uuid=uuid, + year=year, ) try: diff --git a/osxphotos/cli/repl.py b/osxphotos/cli/repl.py index d4c6c3d8..25f71dfd 100644 --- a/osxphotos/cli/repl.py +++ b/osxphotos/cli/repl.py @@ -241,55 +241,58 @@ def _query_options_from_kwargs(**kwargs) -> QueryOptions: """Validate query options and create a QueryOptions instance""" # sanity check input args nonexclusive = [ - "keyword", - "person", + "added_after", + "added_before", + "added_in_last", "album", - "folder", - "name", - "uuid", - "uuid_from_file", + "duplicate", "edited", + "exif", "external_edit", - "uti", - "has_raw", + "folder", "from_date", - "to_date", "from_time", - "to_time", - "year", - "label", + "has_raw", "is_reference", + "keyword", + "label", + "max_size", + "min_size", + "name", + "person", "query_eval", "query_function", - "min_size", - "max_size", "regex", "selected", - "exif", - "duplicate", + "to_date", + "to_time", + "uti", + "uuid_from_file", + "uuid", + "year", ] exclusive = [ - ("favorite", "not_favorite"), - ("hidden", "not_hidden"), - ("missing", "not_missing"), - ("only_photos", "only_movies"), ("burst", "not_burst"), - ("live", "not_live"), ("cloudasset", "not_cloudasset"), - ("incloud", "not_incloud"), - ("portrait", "not_portrait"), - ("screenshot", "not_screenshot"), - ("slow_mo", "not_slow_mo"), - ("time_lapse", "not_time_lapse"), - ("hdr", "not_hdr"), - ("selfie", "not_selfie"), - ("panorama", "not_panorama"), ("deleted", "deleted_only"), - ("shared", "not_shared"), + ("favorite", "not_favorite"), ("has_comment", "no_comment"), ("has_likes", "no_likes"), + ("hdr", "not_hdr"), + ("hidden", "not_hidden"), ("in_album", "not_in_album"), + ("incloud", "not_incloud"), + ("live", "not_live"), ("location", "no_location"), + ("missing", "not_missing"), + ("only_photos", "only_movies"), + ("panorama", "not_panorama"), + ("portrait", "not_portrait"), + ("screenshot", "not_screenshot"), + ("selfie", "not_selfie"), + ("shared", "not_shared"), + ("slow_mo", "not_slow_mo"), + ("time_lapse", "not_time_lapse"), ] # print help if no non-exclusive term or a double exclusive term is given # TODO: add option to validate requiring at least one query arg diff --git a/osxphotos/photosdb/photosdb.py b/osxphotos/photosdb/photosdb.py index bcc079ca..38429663 100644 --- a/osxphotos/photosdb/photosdb.py +++ b/osxphotos/photosdb/photosdb.py @@ -3446,6 +3446,23 @@ class PhotosDB: matching_photos.append(p) photos = matching_photos + if options.added_after: + added_after = options.added_after + if not datetime_has_tz(added_after): + added_after = datetime_naive_to_local(added_after) + photos = [p for p in photos if p.date_added and p.date_added > added_after] + + if options.added_before: + added_before = options.added_before + if not datetime_has_tz(added_before): + added_before = datetime_naive_to_local(added_before) + photos = [p for p in photos if p.date_added and p.date_added < added_before] + + if options.added_in_last: + added_after = datetime.now() - options.added_in_last + added_after = datetime_naive_to_local(added_after) + photos = [p for p in photos if p.date_added and p.date_added > added_after] + if options.function: for function in options.function: photos = function[0](photos) diff --git a/osxphotos/queryoptions.py b/osxphotos/queryoptions.py index 827aa9fd..439d91a4 100644 --- a/osxphotos/queryoptions.py +++ b/osxphotos/queryoptions.py @@ -15,158 +15,163 @@ class QueryOptions: """QueryOptions class for PhotosDB.query Attributes: - keyword: list of keywords to search for - person: list of person names to search for + added_after: search for photos added after a given date + added_before: search for photos added before a given date + added_in_last: search for photos added in last X datetime.timedelta album: list of album names to search for - folder: list of folder names to search for - uuid: list of uuids to search for - title: list of titles to search for - no_title: search for photos with no title + burst_photos: search for burst photos + burst: search for burst photos + cloudasset: search for photos that are managed by iCloud + deleted_only: search only for deleted photos + deleted: also include deleted photos description: list of descriptions to search for - no_description: search for photos with no description - ignore_case: ignore case when searching + duplicate: search for duplicate photos edited: search for edited photos + exif: search for photos with EXIF tags that matches the given data external_edit: search for photos edited in external apps favorite: search for favorite photos - not_favorite: search for non-favorite photos - hidden: search for hidden photos - not_hidden: search for non-hidden photos - missing: search for missing photos - not_missing: search for non-missing photos - shared: search for shared photos - not_shared: search for non-shared photos - photos: search for photos - movies: search for movies - uti: list of UTIs to search for - burst: search for burst photos - not_burst: search for non-burst photos - live: search for live photos - not_live: search for non-live photos - cloudasset: search for photos that are managed by iCloud - not_cloudasset: search for photos that are not managed by iCloud - incloud: search for cloud assets that are synched to iCloud - not_incloud: search for cloud asset photos that are not yet synched to iCloud + folder: list of folder names to search for from_date: search for photos taken on or after this date - to_date: search for photos taken on or before this date - portrait: search for portrait photos - not_portrait: search for non-portrait photos - screenshot: search for screenshot photos - not_screenshot: search for non-screenshot photos - slow_mo: search for slow-mo photos - not_slow_mo: search for non-slow-mo photos - time_lapse: search for time-lapse photos - not_time_lapse: search for non-time-lapse photos - hdr: search for HDR photos - not_hdr: search for non-HDR photos - selfie: search for selfie photos - not_selfie: search for non-selfie photos - panorama: search for panorama photos - not_panorama: search for non-panorama photos - has_raw: search for photos with associated raw files - place: list of place names to search for - no_place: search for photos with no place - label: list of labels to search for - deleted: also include deleted photos - deleted_only: search only for deleted photos - has_comment: search for photos with comments - no_comment: search for photos with no comments - has_likes: search for shared photos with likes - no_likes: search for shared photos with no likes - is_reference: search for photos stored by reference (that is, they are not managed by Photos) - in_album: search for photos in an album - not_in_album: search for photos not in an album - burst_photos: search for burst photos - missing_bursts: for burst photos, also include burst photos that are missing - name: list of names to search for - min_size: minimum size of photos to search for - max_size: maximum size of photos to search for - regex: list of regular expressions to search for - query_eval: list of query expressions to evaluate - duplicate: search for duplicate photos - location: search for photos with a location - no_location: search for photos with no location function: list of query functions to evaluate + has_comment: search for photos with comments + has_likes: search for shared photos with likes + has_raw: search for photos with associated raw files + hdr: search for HDR photos + hidden: search for hidden photos + ignore_case: ignore case when searching + in_album: search for photos in an album + incloud: search for cloud assets that are synched to iCloud + is_reference: search for photos stored by reference (that is, they are not managed by Photos) + keyword: list of keywords to search for + label: list of labels to search for + live: search for live photos + location: search for photos with a location + max_size: maximum size of photos to search for + min_size: minimum size of photos to search for + missing_bursts: for burst photos, also include burst photos that are missing + missing: search for missing photos + movies: search for movies + name: list of names to search for + no_comment: search for photos with no comments + no_description: search for photos with no description + no_likes: search for shared photos with no likes + no_location: search for photos with no location + no_place: search for photos with no place + no_title: search for photos with no title + not_burst: search for non-burst photos + not_cloudasset: search for photos that are not managed by iCloud + not_favorite: search for non-favorite photos + not_hdr: search for non-HDR photos + not_hidden: search for non-hidden photos + not_in_album: search for photos not in an album + not_incloud: search for cloud asset photos that are not yet synched to iCloud + not_live: search for non-live photos + not_missing: search for non-missing photos + not_panorama: search for non-panorama photos + not_portrait: search for non-portrait photos + not_screenshot: search for non-screenshot photos + not_selfie: search for non-selfie photos + not_shared: search for non-shared photos + not_slow_mo: search for non-slow-mo photos + not_time_lapse: search for non-time-lapse photos + panorama: search for panorama photos + person: list of person names to search for + photos: search for photos + place: list of place names to search for + portrait: search for portrait photos + query_eval: list of query expressions to evaluate + regex: list of regular expressions to search for + screenshot: search for screenshot photos selected: search for selected photos - exif: search for photos with EXIF tags that matches the given data + selfie: search for selfie photos + shared: search for shared photos + slow_mo: search for slow-mo photos + time_lapse: search for time-lapse photos + title: list of titles to search for + to_date: search for photos taken on or before this date + uti: list of UTIs to search for + uuid: list of uuids to search for year: search for photos taken in a given year - """ - keyword: Optional[Iterable[str]] = None - person: Optional[Iterable[str]] = None + added_after: Optional[datetime.datetime] = None + added_before: Optional[datetime.datetime] = None + added_in_last: Optional[datetime.timedelta] = None album: Optional[Iterable[str]] = None - folder: Optional[Iterable[str]] = None - uuid: Optional[Iterable[str]] = None - title: Optional[Iterable[str]] = None - no_title: Optional[bool] = None + burst_photos: Optional[bool] = None + burst: Optional[bool] = None + cloudasset: Optional[bool] = None + deleted_only: Optional[bool] = None + deleted: Optional[bool] = None description: Optional[Iterable[str]] = None - no_description: Optional[bool] = None - ignore_case: Optional[bool] = None + duplicate: Optional[bool] = None edited: Optional[bool] = None + exif: Optional[Iterable[Tuple[str, str]]] = None external_edit: Optional[bool] = None favorite: Optional[bool] = None - not_favorite: Optional[bool] = None - hidden: Optional[bool] = None - not_hidden: Optional[bool] = None - missing: Optional[bool] = None - not_missing: Optional[bool] = None - shared: Optional[bool] = None - not_shared: Optional[bool] = None - photos: Optional[bool] = True - movies: Optional[bool] = True - uti: Optional[Iterable[str]] = None - burst: Optional[bool] = None - not_burst: Optional[bool] = None - live: Optional[bool] = None - not_live: Optional[bool] = None - cloudasset: Optional[bool] = None - not_cloudasset: Optional[bool] = None - incloud: Optional[bool] = None - not_incloud: Optional[bool] = None + folder: Optional[Iterable[str]] = None from_date: Optional[datetime.datetime] = None - to_date: Optional[datetime.datetime] = None from_time: Optional[datetime.time] = None - to_time: Optional[datetime.time] = None - portrait: Optional[bool] = None - not_portrait: Optional[bool] = None - screenshot: Optional[bool] = None - not_screenshot: Optional[bool] = None - slow_mo: Optional[bool] = None - not_slow_mo: Optional[bool] = None - time_lapse: Optional[bool] = None - not_time_lapse: Optional[bool] = None - hdr: Optional[bool] = None - not_hdr: Optional[bool] = None - selfie: Optional[bool] = None - not_selfie: Optional[bool] = None - panorama: Optional[bool] = None - not_panorama: Optional[bool] = None - has_raw: Optional[bool] = None - place: Optional[Iterable[str]] = None - no_place: Optional[bool] = None - label: Optional[Iterable[str]] = None - deleted: Optional[bool] = None - deleted_only: Optional[bool] = None - has_comment: Optional[bool] = None - no_comment: Optional[bool] = None - has_likes: Optional[bool] = None - no_likes: Optional[bool] = None - is_reference: Optional[bool] = None - in_album: Optional[bool] = None - not_in_album: Optional[bool] = None - burst_photos: Optional[bool] = None - missing_bursts: Optional[bool] = None - name: Optional[Iterable[str]] = None - min_size: Optional[bitmath.Byte] = None - max_size: Optional[bitmath.Byte] = None - regex: Optional[Iterable[Tuple[str, str]]] = None - query_eval: Optional[Iterable[str]] = None - duplicate: Optional[bool] = None - location: Optional[bool] = None - no_location: Optional[bool] = None function: Optional[List[Tuple[callable, str]]] = None + has_comment: Optional[bool] = None + has_likes: Optional[bool] = None + has_raw: Optional[bool] = None + hdr: Optional[bool] = None + hidden: Optional[bool] = None + ignore_case: Optional[bool] = None + in_album: Optional[bool] = None + incloud: Optional[bool] = None + is_reference: Optional[bool] = None + keyword: Optional[Iterable[str]] = None + label: Optional[Iterable[str]] = None + live: Optional[bool] = None + location: Optional[bool] = None + max_size: Optional[bitmath.Byte] = None + min_size: Optional[bitmath.Byte] = None + missing_bursts: Optional[bool] = None + missing: Optional[bool] = None + movies: Optional[bool] = True + name: Optional[Iterable[str]] = None + no_comment: Optional[bool] = None + no_description: Optional[bool] = None + no_likes: Optional[bool] = None + no_location: Optional[bool] = None + no_place: Optional[bool] = None + no_title: Optional[bool] = None + not_burst: Optional[bool] = None + not_cloudasset: Optional[bool] = None + not_favorite: Optional[bool] = None + not_hdr: Optional[bool] = None + not_hidden: Optional[bool] = None + not_in_album: Optional[bool] = None + not_incloud: Optional[bool] = None + not_live: Optional[bool] = None + not_missing: Optional[bool] = None + not_panorama: Optional[bool] = None + not_portrait: Optional[bool] = None + not_screenshot: Optional[bool] = None + not_selfie: Optional[bool] = None + not_shared: Optional[bool] = None + not_slow_mo: Optional[bool] = None + not_time_lapse: Optional[bool] = None + panorama: Optional[bool] = None + person: Optional[Iterable[str]] = None + photos: Optional[bool] = True + place: Optional[Iterable[str]] = None + portrait: Optional[bool] = None + query_eval: Optional[Iterable[str]] = None + regex: Optional[Iterable[Tuple[str, str]]] = None + screenshot: Optional[bool] = None selected: Optional[bool] = None - exif: Optional[Iterable[Tuple[str, str]]] = None + selfie: Optional[bool] = None + shared: Optional[bool] = None + slow_mo: Optional[bool] = None + time_lapse: Optional[bool] = None + title: Optional[Iterable[str]] = None + to_date: Optional[datetime.datetime] = None + to_time: Optional[datetime.time] = None + uti: Optional[Iterable[str]] = None + uuid: Optional[Iterable[str]] = None year: Optional[Iterable[int]] = None def asdict(self): diff --git a/tests/test_cli.py b/tests/test_cli.py index 07088f04..e9240234 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,5 +1,4 @@ """ Test the command line interface (CLI) """ - import datetime import glob import json @@ -7079,6 +7078,46 @@ def test_query_function(): assert json_got[0]["original_filename"] == "DSC03584.dng" +def test_query_added_after(): + """test query --added-after""" + + runner = CliRunner() + cwd = os.getcwd() + # pylint: disable=not-context-manager + results = runner.invoke( + query, + [ + os.path.join(cwd, PHOTOS_DB_15_7), + "--json", + "--added-after", + "2022-02-03", + ], + ) + assert results.exit_code == 0 + json_got = json.loads(results.output) + assert len(json_got) == 4 + + +def test_query_added_before(): + """test query --added-before""" + + runner = CliRunner() + cwd = os.getcwd() + # pylint: disable=not-context-manager + results = runner.invoke( + query, + [ + os.path.join(cwd, PHOTOS_DB_15_7), + "--json", + "--added-before", + "2019-07-28", + ], + ) + assert results.exit_code == 0 + json_got = json.loads(results.output) + assert len(json_got) == 7 + + def test_export_export_dir_template(): """Test {export_dir} template"""