From cfa2b4a828facf0aff5bc19f777457ad776c4a05 Mon Sep 17 00:00:00 2001 From: mwort Date: Mon, 20 Jan 2020 14:04:50 +0100 Subject: [PATCH] Implement from_date and to_date in PhotosDB as well as query and export command. Some refactoring of CLI as well. --- osxphotos/__main__.py | 161 ++++++++++++--------------------- osxphotos/photosdb.py | 17 +++- tests/test_catalina_10_15_1.py | 17 ++++ tests/test_cli.py | 22 +++++ 4 files changed, 115 insertions(+), 102 deletions(-) diff --git a/osxphotos/__main__.py b/osxphotos/__main__.py index d5999dbf..74333e9d 100644 --- a/osxphotos/__main__.py +++ b/osxphotos/__main__.py @@ -173,6 +173,16 @@ def query_options(f): is_flag=True, help="Search only for photos/images (default searches both images and movies).", ), + o( + "--from-date", + help="Search by start item date, e.g. 2000-01-12T12:00:00 or 2000-12-31 (ISO 8601 w/o TZ).", + type=click.DateTime(), + ), + o( + "--to-date", + help="Search by end item date, e.g. 2000-01-12T12:00:00 or 2000-12-31 (ISO 8601 w/o TZ).", + type=click.DateTime(), + ), ] for o in options[::-1]: f = o(f) @@ -475,6 +485,8 @@ def query( not_cloudasset, incloud, not_incloud, + from_date, + to_date, ): """ Query the Photos database using 1 or more search options; if more than one option is provided, they are treated as "AND" @@ -482,80 +494,33 @@ def query( """ # if no query terms, show help and return - if not any( - [ - keyword, - person, - album, - uuid, - title, - no_title, - description, - no_description, - edited, - external_edit, - favorite, - not_favorite, - hidden, - not_hidden, - missing, - not_missing, - shared, - not_shared, - only_movies, - only_photos, - uti, - burst, - not_burst, - live, - not_live, - cloudasset, - not_cloudasset, - incloud, - not_incloud, - ] - ): - click.echo(cli.commands["query"].get_help(ctx)) - return - elif favorite and not_favorite: - # can't search for both favorite and notfavorite - click.echo(cli.commands["query"].get_help(ctx)) - return - elif hidden and not_hidden: - # can't search for both hidden and nothidden - click.echo(cli.commands["query"].get_help(ctx)) - return - elif missing and not_missing: - # can't search for both missing and notmissing - click.echo(cli.commands["query"].get_help(ctx)) - return - elif title and no_title: - # can't search for both title and no_title - click.echo(cli.commands["query"].get_help(ctx)) - return - elif description and no_description: - # can't search for both description and no_description - click.echo(cli.commands["query"].get_help(ctx)) - return - elif only_photos and only_movies: - # can't have only photos and only movies - click.echo(cli.commands["query"].get_help(ctx)) - return - elif burst and not_burst: - # can't search for both burst and not_burst - click.echo(cli.commands["query"].get_help(ctx)) - return - elif live and not_live: - # can't search for both live and not_live - click.echo(cli.commands["query"].get_help(ctx)) - return - elif cloudasset and not_cloudasset: - # can't search for both live and not_live - click.echo(cli.commands["query"].get_help(ctx)) - return - elif incloud and not_incloud: - # can't search for both live and not_live - click.echo(cli.commands["query"].get_help(ctx)) + # sanity check input args + nonexclusive = [ + keyword, + person, + album, + uuid, + edited, + external_edit, + uti, + from_date, + to_date, + ] + 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), + ] + # print help if no non-exclusive term or a double exclusive term is given + if not any(nonexclusive + [b ^ n for b, n in exclusive]): + click.echo(cli.commands["query"].get_help(ctx), err=True) return # actually have something to query @@ -606,6 +571,8 @@ def query( not_cloudasset=not_cloudasset, incloud=incloud, not_incloud=not_incloud, + from_date=from_date, + to_date=to_date, ) # below needed for to make CliRunner work for testing @@ -698,6 +665,8 @@ def export( not_hidden, shared, not_shared, + from_date, + to_date, verbose, overwrite, export_by_date, @@ -727,33 +696,17 @@ def export( sys.exit("DEST must be valid path") # sanity check input args - if favorite and not_favorite: - # can't search for both favorite and notfavorite - click.echo(cli.commands["export"].get_help(ctx)) - return - elif hidden and not_hidden: - # can't search for both hidden and nothidden - click.echo(cli.commands["export"].get_help(ctx)) - return - elif title and no_title: - # can't search for both title and no_title - click.echo(cli.commands["export"].get_help(ctx)) - return - elif description and no_description: - # can't search for both description and no_description - click.echo(cli.commands["export"].get_help(ctx)) - return - elif only_photos and only_movies: - # can't have only photos and only movies - click.echo(cli.commands["export"].get_help(ctx)) - return - elif burst and not_burst: - # can't search for both burst and not_burst - click.echo(cli.commands["export"].get_help(ctx)) - return - elif live and not_live: - # can't search for both live and not_live - click.echo(cli.commands["export"].get_help(ctx)) + exclusive = [ + (favorite, not_favorite), + (hidden, not_hidden), + (any(title), no_title), + (any(description), no_description), + (only_photos, only_movies), + (burst, not_burst), + (live, not_live), + ] + if any([all(bb) for bb in exclusive]): + click.echo(cli.commands["export"].get_help(ctx), err=True) return isphoto = ismovie = True # default searches for everything @@ -803,6 +756,8 @@ def export( not_cloudasset=False, incloud=False, not_incloud=False, + from_date=from_date, + to_date=to_date, ) if photos: @@ -978,6 +933,8 @@ def _query( not_cloudasset=None, incloud=None, not_incloud=None, + from_date=None, + to_date=None, ): """ run a query against PhotosDB to extract the photos based on user supply criteria """ """ used by query and export commands """ @@ -992,6 +949,8 @@ def _query( uuid=uuid, images=isphoto, movies=ismovie, + from_date=from_date, + to_date=to_date, ) if title: diff --git a/osxphotos/photosdb.py b/osxphotos/photosdb.py index 4d9d983c..2262b7df 100644 --- a/osxphotos/photosdb.py +++ b/osxphotos/photosdb.py @@ -1318,6 +1318,8 @@ class PhotosDB: albums=None, images=True, movies=False, + from_date=None, + to_date=None, ): """ Return a list of PhotoInfo objects @@ -1328,7 +1330,7 @@ class PhotosDB: movies: if True, returns movie files, if False, does not return movies; default is False """ photos_sets = [] # list of photo sets to perform intersection of - if not keywords and not uuid and not persons and not albums: + if not any([keywords, uuid, persons, albums, from_date, to_date]): # return all the photos, filtering for images and movies # append keys of all photos as a single set to photos_sets photos_sets.append(set(self._dbphotos.keys())) @@ -1372,6 +1374,19 @@ class PhotosDB: photos_sets.append(set(self._dbfaces_person[person])) else: logging.debug(f"Could not find person '{person}' in database") + if from_date or to_date: + dsel = self._dbphotos + if from_date: + dsel = { + k: v for k, v in dsel.items() if v["imageDate"] >= from_date + } + logging.debug( + f"Found %i items with from_date {from_date}" % len(dsel) + ) + if to_date: + dsel = {k: v for k, v in dsel.items() if v["imageDate"] <= to_date} + logging.debug(f"Found %i items with to_date {to_date}" % len(dsel)) + photos_sets.append(set(dsel.keys())) photoinfo = [] if photos_sets: # found some photos diff --git a/tests/test_catalina_10_15_1.py b/tests/test_catalina_10_15_1.py index 158f1baa..360f4d85 100644 --- a/tests/test_catalina_10_15_1.py +++ b/tests/test_catalina_10_15_1.py @@ -786,3 +786,20 @@ def test_photosinfo_repr(): k: str(v).encode("utf-8") for k, v in photo2.__dict__.items() } + + +def test_from_to_date(): + import osxphotos + import datetime as dt + + photosdb = osxphotos.PhotosDB(PHOTOS_DB) + + photos = photosdb.photos(from_date=dt.datetime(2018, 10, 28)) + assert len(photos) == 2 + + photos = photosdb.photos(to_date=dt.datetime(2018, 10, 28)) + assert len(photos) == 5 + + photos = photosdb.photos(from_date=dt.datetime(2018, 9, 28), + to_date=dt.datetime(2018, 9, 29)) + assert len(photos) == 4 diff --git a/tests/test_cli.py b/tests/test_cli.py index c3e21ea7..7cf57c3c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -103,3 +103,25 @@ def test_export(): ) files = glob.glob("*.jpg") assert files.sort() == CLI_EXPORT_FILENAMES.sort() + + +def test_query_date(): + import json + import osxphotos + from osxphotos.__main__ import query + + runner = CliRunner() + result = runner.invoke( + query, + [ + "--json", + "--db", + "./tests/Test-10.15.1.photoslibrary", + "--from-date=2018-09-28", + "--to-date=2018-09-28T23:00:00" + ], + ) + assert result.exit_code == 0 + + json_got = json.loads(result.output) + assert len(json_got) == 4 \ No newline at end of file