Files
osxphotos/osxphotos/cli/query.py
Rhet Turnbull 25d6f148be CLI refactor (#642)
* Initial refactoring of cli.py

* Renamed cli_help

* Refactored all cli commands

* Dropped support for 3.7

* Added test for export with --min-size

* Version bump

* Fixed python version
2022-02-26 22:29:19 -08:00

359 lines
8.9 KiB
Python

"""query command for osxphotos CLI"""
import click
import osxphotos
from osxphotos.photosalbum import PhotosAlbum
from osxphotos.queryoptions import QueryOptions
from .common import (
CLI_COLOR_ERROR,
CLI_COLOR_WARNING,
DB_ARGUMENT,
DB_OPTION,
DELETED_OPTIONS,
JSON_OPTION,
OSXPHOTOS_HIDDEN,
QUERY_OPTIONS,
get_photos_db,
load_uuid_from_file,
set_debug,
)
from .list import _list_libraries
from .print_photo_info import print_photo_info
@click.command()
@DB_OPTION
@JSON_OPTION
@QUERY_OPTIONS
@DELETED_OPTIONS
@click.option("--missing", is_flag=True, help="Search for photos missing from disk.")
@click.option(
"--not-missing",
is_flag=True,
help="Search for photos present on disk (e.g. not missing).",
)
@click.option(
"--cloudasset",
is_flag=True,
help="Search for photos that are part of an iCloud library",
)
@click.option(
"--not-cloudasset",
is_flag=True,
help="Search for photos that are not part of an iCloud library",
)
@click.option(
"--incloud",
is_flag=True,
help="Search for photos that are in iCloud (have been synched)",
)
@click.option(
"--not-incloud",
is_flag=True,
help="Search for photos that are not in iCloud (have not been synched)",
)
@click.option(
"--add-to-album",
metavar="ALBUM",
help="Add all photos from query to album ALBUM in Photos. Album ALBUM will be created "
"if it doesn't exist. All photos in the query results will be added to this album. "
"This only works if the Photos library being queried is the last-opened (default) library in Photos. "
"This feature is currently experimental. I don't know how well it will work on large query sets.",
)
@click.option(
"--debug", required=False, is_flag=True, default=False, hidden=OSXPHOTOS_HIDDEN
)
@DB_ARGUMENT
@click.pass_obj
@click.pass_context
def query(
ctx,
cli_obj,
db,
photos_library,
keyword,
person,
album,
folder,
name,
uuid,
uuid_from_file,
title,
no_title,
description,
no_description,
ignore_case,
json_,
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,
from_date,
to_date,
from_time,
to_time,
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,
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,
query_eval,
query_function,
add_to_album,
debug,
):
"""Query the Photos database using 1 or more search options;
if more than one option is provided, they are treated as "AND"
(e.g. search for photos matching all options).
"""
if debug:
set_debug(True)
osxphotos._set_debug(True)
# if no query terms, show help and return
# sanity check input args
nonexclusive = [
keyword,
person,
album,
folder,
name,
uuid,
uuid_from_file,
edited,
external_edit,
uti,
has_raw,
from_date,
to_date,
from_time,
to_time,
label,
is_reference,
query_eval,
query_function,
min_size,
max_size,
regex,
selected,
exif,
duplicate,
]
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),
(deleted, deleted_only),
(shared, not_shared),
(has_comment, no_comment),
(has_likes, no_likes),
(in_album, not_in_album),
(location, no_location),
]
# 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(
nonexclusive + [b ^ n for b, n in exclusive]
):
click.echo("Incompatible query options", err=True)
click.echo(ctx.obj.group.commands["query"].get_help(ctx), err=True)
return
# actually have something to query
# default searches for everything
photos = True
movies = True
if only_movies:
photos = False
if only_photos:
movies = False
# load UUIDs if necessary and append to any uuids passed with --uuid
if uuid_from_file:
uuid_list = list(uuid) # Click option is a tuple
uuid_list.extend(load_uuid_from_file(uuid_from_file))
uuid = tuple(uuid_list)
# below needed for to make CliRunner work for testing
cli_db = cli_obj.db if cli_obj is not None else None
db = get_photos_db(*photos_library, db, cli_db)
if db is None:
click.echo(ctx.obj.group.commands["query"].get_help(ctx), err=True)
click.echo("\n\nLocated the following Photos library databases: ", err=True)
_list_libraries()
return
photosdb = osxphotos.PhotosDB(dbfile=db)
query_options = QueryOptions(
keyword=keyword,
person=person,
album=album,
folder=folder,
uuid=uuid,
title=title,
no_title=no_title,
description=description,
no_description=no_description,
ignore_case=ignore_case,
edited=edited,
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,
from_date=from_date,
to_date=to_date,
from_time=from_time,
to_time=to_time,
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,
regex=regex,
selected=selected,
exif=exif,
duplicate=duplicate,
)
try:
photos = photosdb.query(query_options)
except ValueError as e:
if "Invalid query_eval CRITERIA:" in str(e):
msg = str(e).split(":")[1]
raise click.BadOptionUsage(
"query_eval", f"Invalid query-eval CRITERIA: {msg}"
)
else:
raise ValueError(e)
# below needed for to make CliRunner work for testing
cli_json = cli_obj.json if cli_obj is not None else None
if add_to_album and photos:
album_query = PhotosAlbum(add_to_album, verbose=None)
photo_len = len(photos)
photo_word = "photos" if photo_len > 1 else "photo"
click.echo(
f"Adding {photo_len} {photo_word} to album '{album_query.name}'. Note: Photos may prompt you to confirm this action.",
err=True,
)
try:
album_query.add_list(photos)
except Exception as e:
click.secho(
f"Error adding photos to album {add_to_album}: {e}",
fg=CLI_COLOR_ERROR,
err=True,
)
print_photo_info(photos, cli_json or json_, print_func=click.echo)