Implement from_date and to_date in PhotosDB as well as query and export command. Some refactoring of CLI as well.
This commit is contained in:
@@ -173,6 +173,16 @@ def query_options(f):
|
|||||||
is_flag=True,
|
is_flag=True,
|
||||||
help="Search only for photos/images (default searches both images and movies).",
|
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]:
|
for o in options[::-1]:
|
||||||
f = o(f)
|
f = o(f)
|
||||||
@@ -475,6 +485,8 @@ def query(
|
|||||||
not_cloudasset,
|
not_cloudasset,
|
||||||
incloud,
|
incloud,
|
||||||
not_incloud,
|
not_incloud,
|
||||||
|
from_date,
|
||||||
|
to_date,
|
||||||
):
|
):
|
||||||
""" Query the Photos database using 1 or more search options;
|
""" Query the Photos database using 1 or more search options;
|
||||||
if more than one option is provided, they are treated as "AND"
|
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 no query terms, show help and return
|
||||||
if not any(
|
# sanity check input args
|
||||||
[
|
nonexclusive = [
|
||||||
keyword,
|
keyword,
|
||||||
person,
|
person,
|
||||||
album,
|
album,
|
||||||
uuid,
|
uuid,
|
||||||
title,
|
|
||||||
no_title,
|
|
||||||
description,
|
|
||||||
no_description,
|
|
||||||
edited,
|
edited,
|
||||||
external_edit,
|
external_edit,
|
||||||
favorite,
|
|
||||||
not_favorite,
|
|
||||||
hidden,
|
|
||||||
not_hidden,
|
|
||||||
missing,
|
|
||||||
not_missing,
|
|
||||||
shared,
|
|
||||||
not_shared,
|
|
||||||
only_movies,
|
|
||||||
only_photos,
|
|
||||||
uti,
|
uti,
|
||||||
burst,
|
from_date,
|
||||||
not_burst,
|
to_date,
|
||||||
live,
|
|
||||||
not_live,
|
|
||||||
cloudasset,
|
|
||||||
not_cloudasset,
|
|
||||||
incloud,
|
|
||||||
not_incloud,
|
|
||||||
]
|
]
|
||||||
):
|
exclusive = [
|
||||||
click.echo(cli.commands["query"].get_help(ctx))
|
(favorite, not_favorite),
|
||||||
return
|
(hidden, not_hidden),
|
||||||
elif favorite and not_favorite:
|
(missing, not_missing),
|
||||||
# can't search for both favorite and notfavorite
|
(any(title), no_title),
|
||||||
click.echo(cli.commands["query"].get_help(ctx))
|
(any(description), no_description),
|
||||||
return
|
(only_photos, only_movies),
|
||||||
elif hidden and not_hidden:
|
(burst, not_burst),
|
||||||
# can't search for both hidden and nothidden
|
(live, not_live),
|
||||||
click.echo(cli.commands["query"].get_help(ctx))
|
(cloudasset, not_cloudasset),
|
||||||
return
|
(incloud, not_incloud),
|
||||||
elif missing and not_missing:
|
]
|
||||||
# can't search for both missing and notmissing
|
# print help if no non-exclusive term or a double exclusive term is given
|
||||||
click.echo(cli.commands["query"].get_help(ctx))
|
if not any(nonexclusive + [b ^ n for b, n in exclusive]):
|
||||||
return
|
click.echo(cli.commands["query"].get_help(ctx), err=True)
|
||||||
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))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# actually have something to query
|
# actually have something to query
|
||||||
@@ -606,6 +571,8 @@ def query(
|
|||||||
not_cloudasset=not_cloudasset,
|
not_cloudasset=not_cloudasset,
|
||||||
incloud=incloud,
|
incloud=incloud,
|
||||||
not_incloud=not_incloud,
|
not_incloud=not_incloud,
|
||||||
|
from_date=from_date,
|
||||||
|
to_date=to_date,
|
||||||
)
|
)
|
||||||
|
|
||||||
# below needed for to make CliRunner work for testing
|
# below needed for to make CliRunner work for testing
|
||||||
@@ -698,6 +665,8 @@ def export(
|
|||||||
not_hidden,
|
not_hidden,
|
||||||
shared,
|
shared,
|
||||||
not_shared,
|
not_shared,
|
||||||
|
from_date,
|
||||||
|
to_date,
|
||||||
verbose,
|
verbose,
|
||||||
overwrite,
|
overwrite,
|
||||||
export_by_date,
|
export_by_date,
|
||||||
@@ -727,33 +696,17 @@ def export(
|
|||||||
sys.exit("DEST must be valid path")
|
sys.exit("DEST must be valid path")
|
||||||
|
|
||||||
# sanity check input args
|
# sanity check input args
|
||||||
if favorite and not_favorite:
|
exclusive = [
|
||||||
# can't search for both favorite and notfavorite
|
(favorite, not_favorite),
|
||||||
click.echo(cli.commands["export"].get_help(ctx))
|
(hidden, not_hidden),
|
||||||
return
|
(any(title), no_title),
|
||||||
elif hidden and not_hidden:
|
(any(description), no_description),
|
||||||
# can't search for both hidden and nothidden
|
(only_photos, only_movies),
|
||||||
click.echo(cli.commands["export"].get_help(ctx))
|
(burst, not_burst),
|
||||||
return
|
(live, not_live),
|
||||||
elif title and no_title:
|
]
|
||||||
# can't search for both title and no_title
|
if any([all(bb) for bb in exclusive]):
|
||||||
click.echo(cli.commands["export"].get_help(ctx))
|
click.echo(cli.commands["export"].get_help(ctx), err=True)
|
||||||
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))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
isphoto = ismovie = True # default searches for everything
|
isphoto = ismovie = True # default searches for everything
|
||||||
@@ -803,6 +756,8 @@ def export(
|
|||||||
not_cloudasset=False,
|
not_cloudasset=False,
|
||||||
incloud=False,
|
incloud=False,
|
||||||
not_incloud=False,
|
not_incloud=False,
|
||||||
|
from_date=from_date,
|
||||||
|
to_date=to_date,
|
||||||
)
|
)
|
||||||
|
|
||||||
if photos:
|
if photos:
|
||||||
@@ -978,6 +933,8 @@ def _query(
|
|||||||
not_cloudasset=None,
|
not_cloudasset=None,
|
||||||
incloud=None,
|
incloud=None,
|
||||||
not_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 """
|
""" run a query against PhotosDB to extract the photos based on user supply criteria """
|
||||||
""" used by query and export commands """
|
""" used by query and export commands """
|
||||||
@@ -992,6 +949,8 @@ def _query(
|
|||||||
uuid=uuid,
|
uuid=uuid,
|
||||||
images=isphoto,
|
images=isphoto,
|
||||||
movies=ismovie,
|
movies=ismovie,
|
||||||
|
from_date=from_date,
|
||||||
|
to_date=to_date,
|
||||||
)
|
)
|
||||||
|
|
||||||
if title:
|
if title:
|
||||||
|
|||||||
@@ -1318,6 +1318,8 @@ class PhotosDB:
|
|||||||
albums=None,
|
albums=None,
|
||||||
images=True,
|
images=True,
|
||||||
movies=False,
|
movies=False,
|
||||||
|
from_date=None,
|
||||||
|
to_date=None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Return a list of PhotoInfo objects
|
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
|
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
|
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
|
# return all the photos, filtering for images and movies
|
||||||
# append keys of all photos as a single set to photos_sets
|
# append keys of all photos as a single set to photos_sets
|
||||||
photos_sets.append(set(self._dbphotos.keys()))
|
photos_sets.append(set(self._dbphotos.keys()))
|
||||||
@@ -1372,6 +1374,19 @@ class PhotosDB:
|
|||||||
photos_sets.append(set(self._dbfaces_person[person]))
|
photos_sets.append(set(self._dbfaces_person[person]))
|
||||||
else:
|
else:
|
||||||
logging.debug(f"Could not find person '{person}' in database")
|
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 = []
|
photoinfo = []
|
||||||
if photos_sets: # found some photos
|
if photos_sets: # found some photos
|
||||||
|
|||||||
@@ -786,3 +786,20 @@ def test_photosinfo_repr():
|
|||||||
k: str(v).encode("utf-8")
|
k: str(v).encode("utf-8")
|
||||||
for k, v in photo2.__dict__.items()
|
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
|
||||||
|
|||||||
@@ -103,3 +103,25 @@ def test_export():
|
|||||||
)
|
)
|
||||||
files = glob.glob("*.jpg")
|
files = glob.glob("*.jpg")
|
||||||
assert files.sort() == CLI_EXPORT_FILENAMES.sort()
|
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
|
||||||
Reference in New Issue
Block a user