2020-09-27 17:51:28 +00:00
|
|
|
import argparse
|
2020-12-30 23:32:34 +00:00
|
|
|
import os
|
|
|
|
import re
|
2020-09-27 17:51:28 +00:00
|
|
|
import sys
|
|
|
|
|
2020-12-30 23:47:35 +00:00
|
|
|
from voussoirkit import betterhelp
|
2020-12-07 08:54:53 +00:00
|
|
|
from voussoirkit import interactive
|
2020-09-27 17:51:28 +00:00
|
|
|
from voussoirkit import pathclass
|
2021-01-09 23:41:52 +00:00
|
|
|
from voussoirkit import pipeable
|
2022-09-01 03:21:07 +00:00
|
|
|
from voussoirkit import ratelimiter
|
2020-12-30 23:32:34 +00:00
|
|
|
from voussoirkit import spinal
|
2020-11-16 06:18:40 +00:00
|
|
|
from voussoirkit import stringtools
|
2020-11-09 04:20:04 +00:00
|
|
|
from voussoirkit import vlogging
|
2020-09-27 17:51:28 +00:00
|
|
|
|
|
|
|
import etiquette
|
|
|
|
|
2022-09-01 03:21:07 +00:00
|
|
|
log = vlogging.get_logger(__name__, 'etiquette_cli')
|
|
|
|
|
2022-01-11 01:49:50 +00:00
|
|
|
photodb = None
|
|
|
|
def load_photodb():
|
|
|
|
global photodb
|
|
|
|
if photodb is not None:
|
|
|
|
return
|
|
|
|
photodb = etiquette.photodb.PhotoDB.closest_photodb()
|
|
|
|
|
2020-11-03 08:07:29 +00:00
|
|
|
# HELPERS ##########################################################################################
|
|
|
|
|
2020-12-30 23:32:34 +00:00
|
|
|
def export_symlinks_albums(albums, destination, dry_run):
|
|
|
|
album_directory_names = etiquette.helpers.decollide_names(albums, lambda a: a.display_name)
|
2021-01-02 01:00:03 +00:00
|
|
|
for (album, directory_name) in album_directory_names.items():
|
2020-12-30 23:32:34 +00:00
|
|
|
associated_directories = album.get_associated_directories()
|
|
|
|
if len(associated_directories) == 1:
|
|
|
|
album_dir = associated_directories.pop()
|
2021-01-02 01:00:03 +00:00
|
|
|
directory_name = etiquette.helpers.remove_path_badchars(directory_name)
|
|
|
|
symlink_dir = destination.with_child(directory_name)
|
2020-12-30 23:32:34 +00:00
|
|
|
if dry_run:
|
|
|
|
yield symlink_dir
|
|
|
|
continue
|
2021-01-02 01:00:03 +00:00
|
|
|
if not album_dir.exists:
|
|
|
|
continue
|
2020-12-30 23:32:34 +00:00
|
|
|
if symlink_dir.exists:
|
|
|
|
yield symlink_dir
|
|
|
|
continue
|
|
|
|
print(album, symlink_dir)
|
2021-12-07 20:45:08 +00:00
|
|
|
os.symlink(src=album_dir, dst=symlink_dir)
|
2020-12-30 23:32:34 +00:00
|
|
|
yield symlink_dir
|
|
|
|
|
|
|
|
def export_symlinks_photos(photos, destination, dry_run):
|
|
|
|
photo_filenames = etiquette.helpers.decollide_names(photos, lambda p: p.basename)
|
|
|
|
for (photo, filename) in photo_filenames.items():
|
2021-01-02 01:00:03 +00:00
|
|
|
symlink_path = destination.with_child(filename)
|
2020-12-30 23:32:34 +00:00
|
|
|
if dry_run:
|
2021-01-02 01:00:03 +00:00
|
|
|
yield symlink_path
|
|
|
|
continue
|
|
|
|
if not photo.real_path.exists:
|
2020-12-30 23:32:34 +00:00
|
|
|
continue
|
2021-01-02 01:00:03 +00:00
|
|
|
if symlink_path.exists:
|
|
|
|
yield symlink_path
|
2020-12-30 23:32:34 +00:00
|
|
|
continue
|
2021-01-02 01:00:03 +00:00
|
|
|
print(symlink_path.absolute_path)
|
2021-12-07 20:45:08 +00:00
|
|
|
os.symlink(src=photo.real_path, dst=symlink_path)
|
2021-01-02 01:00:03 +00:00
|
|
|
yield symlink_path
|
2020-12-30 23:32:34 +00:00
|
|
|
|
2020-11-16 01:50:24 +00:00
|
|
|
def get_photos_by_glob(pattern):
|
2022-01-11 01:49:50 +00:00
|
|
|
load_photodb()
|
2020-11-16 01:50:24 +00:00
|
|
|
pattern = pathclass.normalize_sep(pattern)
|
|
|
|
|
|
|
|
if pattern == '**':
|
|
|
|
return search_in_cwd(yield_photos=True, yield_albums=False)
|
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
as_path = pathclass.Path(pattern)
|
|
|
|
if as_path.is_directory:
|
|
|
|
files = as_path.listdir_files()
|
|
|
|
|
|
|
|
else:
|
|
|
|
files = pathclass.glob_files(pattern)
|
|
|
|
|
|
|
|
for file in files:
|
2020-11-16 01:50:24 +00:00
|
|
|
try:
|
|
|
|
photo = photodb.get_photo_by_path(file)
|
2020-12-30 20:30:18 +00:00
|
|
|
yield photo
|
2020-11-16 01:50:24 +00:00
|
|
|
except etiquette.exceptions.NoSuchPhoto:
|
|
|
|
pass
|
|
|
|
|
|
|
|
def get_photos_by_globs(patterns):
|
|
|
|
for pattern in patterns:
|
|
|
|
yield from get_photos_by_glob(pattern)
|
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
def get_photos_from_args(args, fallback_search_in_cwd=False):
|
2022-01-11 01:49:50 +00:00
|
|
|
load_photodb()
|
2020-11-03 08:07:29 +00:00
|
|
|
photos = []
|
2021-11-11 07:45:23 +00:00
|
|
|
|
2022-01-11 01:53:00 +00:00
|
|
|
if args.globs:
|
|
|
|
photos.extend(get_photos_by_globs(args.globs))
|
|
|
|
|
|
|
|
if args.glob:
|
|
|
|
photos.extend(get_photos_by_glob(args.glob))
|
|
|
|
|
2020-11-03 08:07:29 +00:00
|
|
|
if args.photo_id_args:
|
|
|
|
photos.extend(photodb.get_photos_by_id(args.photo_id_args))
|
|
|
|
|
|
|
|
if args.photo_search_args:
|
|
|
|
photos.extend(search_by_argparse(args.photo_search_args, yield_photos=True))
|
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if (not photos) and fallback_search_in_cwd:
|
|
|
|
photos.extend(search_in_cwd(yield_photos=True, yield_albums=False))
|
|
|
|
|
2020-11-03 08:07:29 +00:00
|
|
|
return photos
|
|
|
|
|
|
|
|
def get_albums_from_args(args):
|
2022-01-11 01:49:50 +00:00
|
|
|
load_photodb()
|
2020-11-03 08:07:29 +00:00
|
|
|
albums = []
|
2021-11-11 07:45:23 +00:00
|
|
|
|
2020-11-03 08:07:29 +00:00
|
|
|
if args.album_id_args:
|
|
|
|
albums.extend(photodb.get_albums_by_id(args.album_id_args))
|
|
|
|
|
|
|
|
if args.album_search_args:
|
|
|
|
albums.extend(search_by_argparse(args.album_search_args, yield_albums=True))
|
|
|
|
|
|
|
|
return albums
|
|
|
|
|
|
|
|
def search_in_cwd(**kwargs):
|
2022-01-11 01:49:50 +00:00
|
|
|
load_photodb()
|
2020-11-03 08:07:29 +00:00
|
|
|
cwd = pathclass.cwd()
|
|
|
|
return photodb.search(
|
|
|
|
within_directory=cwd,
|
|
|
|
**kwargs,
|
|
|
|
)
|
|
|
|
|
|
|
|
def search_by_argparse(args, yield_albums=False, yield_photos=False):
|
|
|
|
return search_in_cwd(
|
|
|
|
area=args.area,
|
|
|
|
width=args.width,
|
|
|
|
height=args.height,
|
2022-08-14 01:08:45 +00:00
|
|
|
aspectratio=args.aspectratio,
|
2020-11-03 08:07:29 +00:00
|
|
|
bytes=args.bytes,
|
|
|
|
duration=args.duration,
|
|
|
|
author=args.author,
|
|
|
|
created=args.created,
|
|
|
|
extension=args.extension,
|
|
|
|
extension_not=args.extension_not,
|
|
|
|
filename=args.filename,
|
|
|
|
has_tags=args.has_tags,
|
|
|
|
has_thumbnail=args.has_thumbnail,
|
|
|
|
is_searchhidden=args.is_searchhidden,
|
2021-02-03 20:12:47 +00:00
|
|
|
sha256=args.sha256,
|
2020-11-03 08:07:29 +00:00
|
|
|
mimetype=args.mimetype,
|
|
|
|
tag_musts=args.tag_musts,
|
|
|
|
tag_mays=args.tag_mays,
|
|
|
|
tag_forbids=args.tag_forbids,
|
|
|
|
tag_expression=args.tag_expression,
|
|
|
|
limit=args.limit,
|
|
|
|
offset=args.offset,
|
|
|
|
orderby=args.orderby,
|
|
|
|
yield_albums=yield_albums,
|
|
|
|
yield_photos=yield_photos,
|
|
|
|
)
|
|
|
|
|
2021-09-30 21:43:45 +00:00
|
|
|
# ARGPARSE #########################################################################################
|
2020-09-28 21:25:10 +00:00
|
|
|
|
2020-11-27 23:39:58 +00:00
|
|
|
def add_remove_tag_argparse(args, action):
|
2022-01-11 01:49:50 +00:00
|
|
|
load_photodb()
|
2020-11-03 08:07:29 +00:00
|
|
|
|
2022-01-11 01:53:00 +00:00
|
|
|
tag = photodb.get_tag_by_name(args.tag_name)
|
|
|
|
|
|
|
|
if args.any_photo_args:
|
2020-11-03 08:07:29 +00:00
|
|
|
photos = get_photos_from_args(args)
|
|
|
|
else:
|
2022-02-13 03:56:00 +00:00
|
|
|
# todo: require the user to provide some photo args, dont implicitly do all under cwd.
|
2020-11-06 02:21:33 +00:00
|
|
|
photos = search_in_cwd(yield_photos=True, yield_albums=False)
|
2020-11-03 08:07:29 +00:00
|
|
|
|
2022-01-11 01:53:00 +00:00
|
|
|
need_commit = False
|
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
with photodb.transaction:
|
|
|
|
for photo in photos:
|
|
|
|
if action == 'add':
|
|
|
|
photo.add_tag(tag)
|
|
|
|
elif action == 'remove':
|
|
|
|
photo.remove_tag(tag)
|
|
|
|
need_commit = True
|
2022-01-11 01:53:00 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if not need_commit:
|
|
|
|
return 0
|
2022-01-11 01:53:00 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if not (args.autoyes or interactive.getpermission('Commit?')):
|
|
|
|
photodb.rollback()
|
|
|
|
return 1
|
2022-01-11 01:53:00 +00:00
|
|
|
|
|
|
|
return 0
|
2020-11-03 08:07:29 +00:00
|
|
|
|
2022-01-11 01:53:29 +00:00
|
|
|
def delete_albums_argparse(args):
|
|
|
|
load_photodb()
|
|
|
|
|
|
|
|
need_commit = False
|
|
|
|
albums = get_albums_from_args(args)
|
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
with photodb.transaction:
|
|
|
|
for album in albums:
|
|
|
|
album.delete()
|
|
|
|
need_commit = True
|
|
|
|
|
|
|
|
if not need_commit:
|
|
|
|
return 0
|
2022-01-11 01:53:29 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if not (args.autoyes or interactive.getpermission('Commit?')):
|
|
|
|
photodb.rollback()
|
|
|
|
return 1
|
2020-11-03 08:07:29 +00:00
|
|
|
|
2021-10-16 03:58:10 +00:00
|
|
|
return 0
|
|
|
|
|
2022-01-11 01:53:29 +00:00
|
|
|
def delete_photos_argparse(args):
|
2022-01-11 01:49:50 +00:00
|
|
|
load_photodb()
|
2021-01-29 01:03:19 +00:00
|
|
|
|
|
|
|
need_commit = False
|
2022-01-11 01:53:29 +00:00
|
|
|
photos = get_photos_from_args(args)
|
2021-01-29 01:03:19 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
with photodb.transaction:
|
|
|
|
for photo in photos:
|
|
|
|
photo.delete(delete_file=args.delete_file)
|
|
|
|
need_commit = True
|
|
|
|
|
|
|
|
if not need_commit:
|
|
|
|
return 0
|
2021-01-29 01:03:19 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if not (args.autoyes or interactive.getpermission('Commit?')):
|
|
|
|
photodb.rollback()
|
|
|
|
return 1
|
2021-01-29 01:03:19 +00:00
|
|
|
|
2021-10-16 03:58:10 +00:00
|
|
|
return 0
|
|
|
|
|
2020-09-28 21:28:15 +00:00
|
|
|
def digest_directory_argparse(args):
|
2021-02-03 20:12:47 +00:00
|
|
|
directories = pipeable.input(args.directory, strip=True, skip_blank=True)
|
|
|
|
directories = [pathclass.Path(d) for d in directories]
|
|
|
|
for directory in directories:
|
|
|
|
directory.assert_is_directory()
|
|
|
|
|
2022-01-11 01:49:50 +00:00
|
|
|
load_photodb()
|
2021-02-03 20:12:47 +00:00
|
|
|
need_commit = False
|
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
with photodb.transaction:
|
|
|
|
for directory in directories:
|
|
|
|
digest = photodb.digest_directory(
|
|
|
|
directory,
|
|
|
|
exclude_directories=args.exclude_directories,
|
|
|
|
exclude_filenames=args.exclude_filenames,
|
|
|
|
glob_directories=args.glob_directories,
|
|
|
|
glob_filenames=args.glob_filenames,
|
|
|
|
hash_kwargs={'bytes_per_second': args.hash_bytes_per_second},
|
|
|
|
make_albums=args.make_albums,
|
|
|
|
new_photo_ratelimit=args.ratelimit,
|
|
|
|
recurse=args.recurse,
|
|
|
|
yield_albums=True,
|
2022-08-21 21:02:39 +00:00
|
|
|
yield_new_photos=True,
|
|
|
|
yield_old_photos=False,
|
2022-07-16 06:00:07 +00:00
|
|
|
)
|
|
|
|
for result in digest:
|
|
|
|
# print(result)
|
|
|
|
need_commit = True
|
2021-02-03 20:12:47 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if not need_commit:
|
|
|
|
return 0
|
2020-09-28 21:28:15 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if not (args.autoyes or interactive.getpermission('Commit?')):
|
|
|
|
photodb.rollback()
|
|
|
|
return 1
|
2020-09-28 21:28:15 +00:00
|
|
|
|
2021-10-16 03:58:10 +00:00
|
|
|
return 0
|
|
|
|
|
2020-10-18 02:18:25 +00:00
|
|
|
def easybake_argparse(args):
|
2022-01-11 01:49:50 +00:00
|
|
|
load_photodb()
|
2020-11-03 08:07:29 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
with photodb.transaction:
|
|
|
|
for eb_string in args.eb_strings:
|
|
|
|
notes = photodb.easybake(eb_string)
|
|
|
|
for (action, tagname) in notes:
|
|
|
|
print(action, tagname)
|
|
|
|
|
|
|
|
if not (args.autoyes or interactive.getpermission('Commit?')):
|
|
|
|
photodb.rollback()
|
|
|
|
return 1
|
2020-11-03 08:07:29 +00:00
|
|
|
|
2021-10-16 03:58:10 +00:00
|
|
|
return 0
|
|
|
|
|
2020-12-30 23:32:34 +00:00
|
|
|
def export_symlinks_argparse(args):
|
|
|
|
destination = pathclass.Path(args.destination)
|
|
|
|
destination.makedirs(exist_ok=True)
|
|
|
|
|
|
|
|
total_paths = set()
|
|
|
|
|
2022-01-11 01:53:00 +00:00
|
|
|
if args.any_album_args:
|
2021-01-29 01:04:38 +00:00
|
|
|
albums = get_albums_from_args(args)
|
|
|
|
export = export_symlinks_albums(
|
|
|
|
albums,
|
|
|
|
destination,
|
|
|
|
dry_run=args.dry_run,
|
|
|
|
)
|
|
|
|
total_paths.update(export)
|
2020-12-30 23:32:34 +00:00
|
|
|
|
2022-01-11 01:53:00 +00:00
|
|
|
if args.any_photo_args:
|
2021-01-29 01:04:38 +00:00
|
|
|
photos = get_photos_from_args(args)
|
|
|
|
export = export_symlinks_photos(
|
|
|
|
photos,
|
|
|
|
destination,
|
|
|
|
dry_run=args.dry_run,
|
|
|
|
)
|
|
|
|
total_paths.update(export)
|
2020-12-30 23:32:34 +00:00
|
|
|
|
2021-01-29 01:05:20 +00:00
|
|
|
if not args.prune or args.dry_run:
|
2021-10-16 03:58:10 +00:00
|
|
|
return 0
|
2021-01-29 01:05:20 +00:00
|
|
|
|
2021-05-18 00:01:11 +00:00
|
|
|
symlinks = spinal.walk(destination, yield_directories=True, yield_files=True)
|
2021-01-29 01:05:20 +00:00
|
|
|
symlinks = set(path for path in symlinks if path.is_link)
|
|
|
|
symlinks = symlinks.difference(total_paths)
|
|
|
|
for old_symlink in symlinks:
|
|
|
|
print(f'Pruning {old_symlink}.')
|
2021-12-07 20:45:08 +00:00
|
|
|
os.remove(old_symlink)
|
2021-01-29 01:05:20 +00:00
|
|
|
if not old_symlink.parent.listdir():
|
2021-12-07 20:45:08 +00:00
|
|
|
os.rmdir(old_symlink.parent)
|
2021-01-29 01:05:20 +00:00
|
|
|
|
2021-05-18 00:01:11 +00:00
|
|
|
checkdirs = set(spinal.walk(destination, yield_directories=True, yield_files=False))
|
2021-01-29 01:05:20 +00:00
|
|
|
while checkdirs:
|
|
|
|
check = checkdirs.pop()
|
|
|
|
if check not in destination:
|
|
|
|
continue
|
|
|
|
if len(check.listdir()) == 0:
|
2021-12-07 20:45:08 +00:00
|
|
|
os.rmdir(check)
|
2021-01-29 01:05:20 +00:00
|
|
|
checkdirs.add(check.parent)
|
2020-12-30 23:32:34 +00:00
|
|
|
|
2021-10-16 03:58:10 +00:00
|
|
|
return 0
|
|
|
|
|
2021-06-04 02:18:13 +00:00
|
|
|
def generate_thumbnail_argparse(args):
|
2022-01-11 01:49:50 +00:00
|
|
|
load_photodb()
|
2021-06-04 02:18:13 +00:00
|
|
|
|
2022-01-11 01:53:00 +00:00
|
|
|
if args.any_photo_args:
|
2021-06-04 02:18:13 +00:00
|
|
|
photos = get_photos_from_args(args)
|
|
|
|
else:
|
|
|
|
photos = search_in_cwd(yield_photos=True, yield_albums=False)
|
|
|
|
|
|
|
|
need_commit = False
|
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
with photodb.transaction:
|
|
|
|
try:
|
|
|
|
for photo in photos:
|
|
|
|
photo.generate_thumbnail()
|
|
|
|
need_commit = True
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
pass
|
|
|
|
|
|
|
|
if not need_commit:
|
|
|
|
return 0
|
2021-06-04 02:18:13 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if not (args.autoyes or interactive.getpermission('Commit?')):
|
|
|
|
photodb.rollback()
|
|
|
|
return 1
|
2021-06-04 02:18:13 +00:00
|
|
|
|
2021-10-16 03:58:10 +00:00
|
|
|
return 0
|
|
|
|
|
2020-11-03 08:07:29 +00:00
|
|
|
def init_argparse(args):
|
2022-03-09 01:03:50 +00:00
|
|
|
global photodb
|
|
|
|
try:
|
|
|
|
load_photodb()
|
|
|
|
except etiquette.exceptions.NoClosestPhotoDB:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
pipeable.stderr(f'PhotoDB {photodb} already exists.')
|
|
|
|
return 0
|
2020-12-30 05:33:45 +00:00
|
|
|
photodb = etiquette.photodb.PhotoDB(create=True)
|
2021-10-16 03:58:10 +00:00
|
|
|
return 0
|
2020-10-18 02:18:25 +00:00
|
|
|
|
2022-09-01 03:21:07 +00:00
|
|
|
def new_photo_argparse(args):
|
|
|
|
load_photodb()
|
|
|
|
|
|
|
|
limiter = ratelimiter.Ratelimiter(allowance=1, period=args.ratelimit)
|
|
|
|
need_commit = False
|
|
|
|
|
|
|
|
with photodb.transaction:
|
|
|
|
photos = []
|
|
|
|
for file in pathclass.glob_many_files(args.globs):
|
|
|
|
try:
|
|
|
|
exists = photodb.get_photo_by_path(file)
|
|
|
|
log.info('%s exists.', file.absolute_path)
|
|
|
|
continue
|
|
|
|
except etiquette.exceptions.NoSuchPhoto:
|
|
|
|
pass
|
|
|
|
limiter.limit()
|
|
|
|
photo = photodb.new_photo(file)
|
|
|
|
photos.append(photo)
|
|
|
|
need_commit = True
|
|
|
|
|
|
|
|
if not need_commit:
|
|
|
|
return 0
|
|
|
|
|
|
|
|
if args.make_album or args.album_title:
|
|
|
|
album_title = args.album_title or None
|
|
|
|
album = photodb.new_album(title=album_title)
|
|
|
|
album.add_photos(photos)
|
|
|
|
|
|
|
|
if not (args.autoyes or interactive.getpermission('Commit?')):
|
|
|
|
photodb.rollback()
|
|
|
|
return 1
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
2020-12-30 05:03:01 +00:00
|
|
|
def purge_deleted_files_argparse(args):
|
2022-01-11 01:49:50 +00:00
|
|
|
load_photodb()
|
2020-12-30 23:35:15 +00:00
|
|
|
|
2022-01-11 01:53:00 +00:00
|
|
|
if args.any_photo_args:
|
2020-12-30 23:35:15 +00:00
|
|
|
photos = get_photos_from_args(args)
|
|
|
|
else:
|
|
|
|
photos = search_in_cwd(yield_photos=True, yield_albums=False)
|
|
|
|
|
2021-06-04 02:08:31 +00:00
|
|
|
need_commit = False
|
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
with photodb.transaction:
|
|
|
|
for deleted in photodb.purge_deleted_files(photos):
|
|
|
|
need_commit = True
|
|
|
|
print(deleted)
|
2020-12-30 23:35:15 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if not need_commit:
|
|
|
|
return 0
|
2021-06-04 02:08:31 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if not (args.autoyes or interactive.getpermission('Commit?')):
|
|
|
|
photodb.rollback()
|
|
|
|
return 1
|
2020-09-30 20:44:09 +00:00
|
|
|
|
2021-10-16 03:58:10 +00:00
|
|
|
return 0
|
|
|
|
|
2020-09-30 20:44:09 +00:00
|
|
|
def purge_empty_albums_argparse(args):
|
2022-01-11 01:49:50 +00:00
|
|
|
load_photodb()
|
2020-12-30 23:35:15 +00:00
|
|
|
|
|
|
|
# We do not check args.album_search_args because currently it is not
|
|
|
|
# possible for search results to find empty albums on account of the fact
|
|
|
|
# that albums are only yielded when they contain some result photo.
|
|
|
|
if args.album_id_args:
|
|
|
|
albums = get_albums_from_args(args)
|
|
|
|
else:
|
|
|
|
albums = photodb.get_albums_within_directory(pathclass.cwd())
|
|
|
|
|
2021-06-04 02:08:31 +00:00
|
|
|
need_commit = False
|
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
with photodb.transaction:
|
|
|
|
for deleted in photodb.purge_empty_albums(albums):
|
|
|
|
need_commit = True
|
|
|
|
print(deleted)
|
2020-11-03 08:07:29 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if not need_commit:
|
|
|
|
return 0
|
2021-06-04 02:08:31 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if not (args.autoyes or interactive.getpermission('Commit?')):
|
|
|
|
photodb.rollback()
|
|
|
|
return 1
|
2020-09-30 20:44:09 +00:00
|
|
|
|
2021-10-16 03:58:10 +00:00
|
|
|
return 0
|
|
|
|
|
2021-02-03 20:12:47 +00:00
|
|
|
def reload_metadata_argparse(args):
|
2022-01-11 01:49:50 +00:00
|
|
|
load_photodb()
|
2021-02-03 20:12:47 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
photos = get_photos_from_args(args)
|
2021-02-03 20:12:47 +00:00
|
|
|
|
|
|
|
hash_kwargs = {
|
|
|
|
'bytes_per_second': args.hash_bytes_per_second,
|
|
|
|
'callback_progress': spinal.callback_progress_v1,
|
|
|
|
}
|
|
|
|
|
|
|
|
need_commit = False
|
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
with photodb.transaction:
|
|
|
|
try:
|
|
|
|
for photo in photos:
|
|
|
|
if not photo.real_path.is_file:
|
|
|
|
continue
|
|
|
|
|
|
|
|
need_reload = (
|
|
|
|
args.force or
|
|
|
|
photo.mtime != photo.real_path.stat.st_mtime or
|
|
|
|
photo.bytes != photo.real_path.stat.st_size
|
|
|
|
)
|
|
|
|
|
|
|
|
if not need_reload:
|
|
|
|
continue
|
|
|
|
photo.reload_metadata(hash_kwargs=hash_kwargs)
|
|
|
|
need_commit = True
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
pass
|
2021-02-03 20:12:47 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if not need_commit:
|
|
|
|
return 0
|
2021-02-03 20:12:47 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if not (args.autoyes or interactive.getpermission('Commit?')):
|
|
|
|
photodb.rollback()
|
|
|
|
return 1
|
2021-02-03 20:12:47 +00:00
|
|
|
|
2021-10-16 03:58:10 +00:00
|
|
|
return 0
|
|
|
|
|
2021-01-03 10:58:18 +00:00
|
|
|
def relocate_argparse(args):
|
2022-01-11 01:49:50 +00:00
|
|
|
load_photodb()
|
2021-01-03 10:58:18 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
with photodb.transaction:
|
|
|
|
photo = photodb.get_photo(args.photo_id)
|
|
|
|
photo.relocate(args.filepath)
|
2021-01-03 10:58:18 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if not (args.autoyes or interactive.getpermission('Commit?')):
|
|
|
|
photodb.rollback()
|
|
|
|
return 1
|
2021-01-03 10:58:18 +00:00
|
|
|
|
2021-10-16 03:58:10 +00:00
|
|
|
return 0
|
|
|
|
|
2020-09-28 21:25:10 +00:00
|
|
|
def search_argparse(args):
|
|
|
|
photos = search_by_argparse(args, yield_photos=True)
|
2020-09-27 17:51:28 +00:00
|
|
|
for photo in photos:
|
|
|
|
print(photo.real_path.absolute_path)
|
|
|
|
|
2021-10-16 03:58:10 +00:00
|
|
|
return 0
|
|
|
|
|
2020-11-16 01:49:28 +00:00
|
|
|
def show_associated_directories_argparse(args):
|
2022-01-11 01:53:00 +00:00
|
|
|
if args.any_album_args:
|
2020-11-16 01:49:28 +00:00
|
|
|
albums = get_albums_from_args(args)
|
|
|
|
else:
|
|
|
|
albums = search_in_cwd(yield_photos=False, yield_albums=True)
|
|
|
|
|
|
|
|
for album in albums:
|
|
|
|
directories = album.get_associated_directories()
|
|
|
|
if not directories:
|
|
|
|
continue
|
|
|
|
directories = [f'"{d.absolute_path}"' for d in directories]
|
|
|
|
directories = ' '.join(directories)
|
|
|
|
print(f'{album} | {directories}')
|
|
|
|
|
2021-10-16 03:58:10 +00:00
|
|
|
return 0
|
|
|
|
|
2020-11-03 08:07:29 +00:00
|
|
|
def set_unset_searchhidden_argparse(args, searchhidden):
|
2022-01-11 01:49:50 +00:00
|
|
|
load_photodb()
|
2020-11-03 08:07:29 +00:00
|
|
|
|
|
|
|
if args.photo_search_args:
|
|
|
|
args.photo_search_args.is_searchhidden = not searchhidden
|
|
|
|
|
|
|
|
if args.album_search_args:
|
|
|
|
args.album_search_args.is_searchhidden = not searchhidden
|
|
|
|
|
2022-01-11 01:53:00 +00:00
|
|
|
photos = []
|
|
|
|
if args.any_photo_args:
|
|
|
|
photos.extend(get_photos_from_args(args))
|
|
|
|
if args.any_album_args:
|
2020-11-06 02:22:19 +00:00
|
|
|
albums = get_albums_from_args(args)
|
|
|
|
photos.extend(photo for album in albums for photo in album.walk_photos())
|
|
|
|
else:
|
|
|
|
photos = search_in_cwd(yield_photos=True, yield_albums=False)
|
2020-11-03 08:07:29 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
with photodb.transaction:
|
|
|
|
for photo in photos:
|
|
|
|
print(photo)
|
|
|
|
photo.set_searchhidden(searchhidden)
|
2020-11-03 08:07:29 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if not (args.autoyes or interactive.getpermission('Commit?')):
|
|
|
|
photodb.rollback()
|
|
|
|
return 1
|
2020-11-03 08:07:29 +00:00
|
|
|
|
2021-10-16 03:58:10 +00:00
|
|
|
return 0
|
|
|
|
|
2020-10-18 02:12:22 +00:00
|
|
|
def tag_breplace_argparse(args):
|
2022-01-11 01:49:50 +00:00
|
|
|
load_photodb()
|
2020-10-18 02:12:22 +00:00
|
|
|
renames = []
|
|
|
|
tag_names = photodb.get_all_tag_names()
|
|
|
|
all_names = tag_names.union(photodb.get_all_synonyms())
|
|
|
|
for tag_name in tag_names:
|
|
|
|
if args.regex:
|
|
|
|
new_name = re.sub(args.replace_from, args.replace_to, tag_name)
|
|
|
|
else:
|
|
|
|
new_name = tag_name.replace(args.replace_from, args.replace_to)
|
|
|
|
new_name = photodb.normalize_tagname(new_name)
|
|
|
|
if new_name == tag_name:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if new_name in all_names:
|
|
|
|
raise etiquette.exceptions.TagExists(new_name)
|
|
|
|
|
|
|
|
if args.set_synonym:
|
|
|
|
printline = f'{tag_name} -> {new_name}+{tag_name}'
|
|
|
|
else:
|
|
|
|
printline = f'{tag_name} -> {new_name}'
|
|
|
|
|
|
|
|
renames.append((tag_name, new_name, printline))
|
|
|
|
|
|
|
|
if not args.autoyes:
|
|
|
|
for (tag_name, new_name, printline) in renames:
|
|
|
|
print(printline)
|
2020-12-07 08:54:53 +00:00
|
|
|
if not interactive.getpermission('Ok?', must_pick=True):
|
2022-07-16 06:00:07 +00:00
|
|
|
return 1
|
2020-10-18 02:12:22 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
with photodb.transaction:
|
|
|
|
for (tag_name, new_name, printline) in renames:
|
|
|
|
print(printline)
|
|
|
|
tag = photodb.get_tag(tag_name)
|
|
|
|
tag.rename(new_name)
|
|
|
|
if args.set_synonym:
|
|
|
|
tag.add_synonym(tag_name)
|
2021-10-03 06:39:43 +00:00
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if not (args.autoyes or interactive.getpermission('Commit?')):
|
|
|
|
photodb.rollback()
|
|
|
|
return 1
|
2020-10-18 02:12:22 +00:00
|
|
|
|
2021-10-16 03:58:10 +00:00
|
|
|
return 0
|
|
|
|
|
2021-05-16 01:20:59 +00:00
|
|
|
def tag_list_argparse(args):
|
2022-01-11 01:49:50 +00:00
|
|
|
load_photodb()
|
2021-05-16 01:20:59 +00:00
|
|
|
tags = photodb.get_all_tag_names()
|
|
|
|
synonyms = photodb.get_all_synonyms()
|
|
|
|
keys = sorted(tags.union(synonyms.keys()))
|
|
|
|
for key in keys:
|
|
|
|
if key in synonyms:
|
|
|
|
print(f'{key}={synonyms[key]}')
|
|
|
|
else:
|
|
|
|
print(key)
|
|
|
|
|
2021-10-16 03:58:10 +00:00
|
|
|
return 0
|
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
@vlogging.main_decorator
|
|
|
|
def main(argv):
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
description='''
|
|
|
|
This is the command-line interface for Etiquette, so that you can automate your
|
|
|
|
database and integrate it into other scripts.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
subparsers = parser.add_subparsers()
|
2020-12-30 23:47:35 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
################################################################################################
|
2021-01-15 08:34:05 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
p_add_tag = subparsers.add_parser(
|
|
|
|
'add_tag',
|
|
|
|
aliases=['add-tag'],
|
|
|
|
description='''
|
|
|
|
Add a tag to photos by a filename glob or by search results.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_add_tag.examples = [
|
|
|
|
'wallpaper wall*.jpg wall*.png',
|
|
|
|
{'args': 'author.voussoir --photo-search --tag-forbids author', 'comment': 'Add an author tag to all photos that don\'t have one.'}
|
|
|
|
]
|
|
|
|
p_add_tag.add_argument(
|
|
|
|
'tag_name',
|
|
|
|
)
|
|
|
|
p_add_tag.add_argument(
|
|
|
|
'globs',
|
|
|
|
nargs='*',
|
|
|
|
help='''
|
|
|
|
Select Photos by using glob patterns that match files.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_add_tag.add_argument(
|
|
|
|
'--photos',
|
|
|
|
dest='photo_id_args',
|
|
|
|
metavar='photo_id',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will be treated as IDs of Photos to tag.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_add_tag.add_argument(
|
|
|
|
'--photo_search',
|
|
|
|
'--photo-search',
|
|
|
|
dest='photo_search_args',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will go to the search command to generate the
|
|
|
|
list of Photos to tag. See search --help for help.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_add_tag.add_argument(
|
|
|
|
'--yes',
|
|
|
|
dest='autoyes',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
|
|
|
Commit the database without prompting.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_add_tag.set_defaults(func=lambda args: add_remove_tag_argparse(args, action='add'))
|
2021-09-13 04:20:18 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
################################################################################################
|
2020-12-30 23:47:35 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
p_remove_tag = subparsers.add_parser(
|
|
|
|
'remove_tag',
|
|
|
|
aliases=['remove-tag'],
|
|
|
|
description='''
|
|
|
|
Remove a tag from photos by a filename glob or by search results.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_remove_tag.examples = [
|
|
|
|
'watchlist spongebob*.mp4',
|
|
|
|
'watchlist --photo-search --tag-musts directed_by_michael_bay',
|
|
|
|
]
|
|
|
|
p_remove_tag.add_argument(
|
|
|
|
'tag_name',
|
|
|
|
)
|
|
|
|
p_remove_tag.add_argument(
|
|
|
|
'globs',
|
|
|
|
nargs='*',
|
|
|
|
help='''
|
|
|
|
Select Photos by using glob patterns that match files.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_remove_tag.add_argument(
|
|
|
|
'--photos',
|
|
|
|
dest='photo_id_args',
|
|
|
|
metavar='photo_id',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will be treated as IDs of Photos to untag.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_remove_tag.add_argument(
|
|
|
|
'--photo_search',
|
|
|
|
'--photo-search',
|
|
|
|
dest='photo_search_args',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will go to the search command to generate the
|
|
|
|
list of Photos to untag. See search --help for help.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_remove_tag.add_argument(
|
|
|
|
'--yes',
|
|
|
|
dest='autoyes',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
|
|
|
Commit the database without prompting.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_remove_tag.set_defaults(func=lambda args: add_remove_tag_argparse(args, action='remove'))
|
2022-01-11 01:53:29 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
################################################################################################
|
2022-01-11 01:53:29 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
p_delete_albums = subparsers.add_parser(
|
|
|
|
'delete_albums',
|
|
|
|
aliases=['delete-albums'],
|
|
|
|
description='''
|
|
|
|
Remove albums from the database.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_delete_albums.add_argument(
|
|
|
|
'--albums',
|
|
|
|
dest='album_id_args',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will be treated as IDs of Albums to delete.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_delete_albums.add_argument(
|
|
|
|
'--album_search',
|
|
|
|
'--album-search',
|
|
|
|
dest='album_search_args',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will go to the search command to generate the
|
|
|
|
list of Albums to delete. See search --help for help.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_delete_albums.add_argument(
|
|
|
|
'--yes',
|
|
|
|
dest='autoyes',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
|
|
|
Commit the database without prompting.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_delete_albums.set_defaults(func=delete_albums_argparse)
|
2022-01-11 01:53:29 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
################################################################################################
|
2021-01-29 01:03:19 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
p_delete_photos = subparsers.add_parser(
|
|
|
|
'delete_photos',
|
|
|
|
aliases=['delete-photos'],
|
|
|
|
description='''
|
|
|
|
Remove photos from the database.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_delete_photos.add_argument(
|
|
|
|
'globs',
|
|
|
|
nargs='*',
|
|
|
|
help='''
|
|
|
|
Select Photos by using glob patterns that match files.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_delete_photos.add_argument(
|
|
|
|
'--delete_file',
|
|
|
|
'--delete-file',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
2021-05-31 02:15:03 +00:00
|
|
|
Delete the file from disk after committing.
|
|
|
|
Your config.json file's recycle_instead_of_delete will influence this.
|
|
|
|
Without this flag, photos are removed from the db but remain on disk.
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_delete_photos.add_argument(
|
|
|
|
'--photos',
|
|
|
|
dest='photo_id_args',
|
|
|
|
metavar='photo_id',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will be treated as IDs of Photos to delete.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_delete_photos.add_argument(
|
|
|
|
'--photo_search',
|
|
|
|
'--photo-search',
|
|
|
|
dest='photo_search_args',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will go to the search command to generate the
|
|
|
|
list of Photos to delete. See search --help for help.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_delete_photos.add_argument(
|
|
|
|
'--yes',
|
|
|
|
dest='autoyes',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
|
|
|
Commit the database without prompting.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_delete_photos.set_defaults(func=delete_photos_argparse)
|
2021-05-31 02:15:03 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
################################################################################################
|
2020-12-30 23:47:35 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
p_digest = subparsers.add_parser(
|
|
|
|
'digest',
|
|
|
|
aliases=['digest_directory', 'digest-directory'],
|
|
|
|
description='''
|
|
|
|
Digest a directory, adding new files as Photos and folders as Albums into
|
|
|
|
the database.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_digest.examples = [
|
|
|
|
'media --ratelimit 1',
|
|
|
|
'photos --no-recurse --no-albums --ratelimit 0.25',
|
|
|
|
'. --glob-filenames *.jpg --exclude-filenames thumb*',
|
|
|
|
]
|
|
|
|
p_digest.add_argument(
|
|
|
|
'directory',
|
|
|
|
)
|
|
|
|
p_digest.add_argument(
|
|
|
|
'--exclude_directories',
|
|
|
|
'--exclude-directories',
|
|
|
|
metavar='pattern',
|
|
|
|
nargs='+',
|
|
|
|
default=None,
|
|
|
|
help='''
|
|
|
|
Any directories matching any of these patterns will be skipped.
|
2021-08-21 05:32:19 +00:00
|
|
|
These patterns may be absolute paths like 'D:\\temp', plain names like
|
2021-02-03 20:12:47 +00:00
|
|
|
'thumbnails' or glob patterns like 'build_*'.
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_digest.add_argument(
|
|
|
|
'--exclude_filenames',
|
|
|
|
'--exclude-filenames',
|
|
|
|
metavar='pattern',
|
|
|
|
nargs='+',
|
|
|
|
default=None,
|
|
|
|
help='''
|
|
|
|
Any filenames matching any of these patterns will be skipped.
|
2021-08-21 05:32:19 +00:00
|
|
|
These patterns may be absolute paths like 'D:\\somewhere\\config.json',
|
2021-02-03 20:12:47 +00:00
|
|
|
plain names like 'thumbs.db' or glob patterns like '*.temp'.
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_digest.add_argument(
|
|
|
|
'--glob_directories',
|
|
|
|
'--glob-directories',
|
|
|
|
metavar='pattern',
|
|
|
|
nargs='+',
|
|
|
|
default=None,
|
|
|
|
help='''
|
|
|
|
Only directories matching any of these patterns will be digested.
|
2021-02-03 20:12:47 +00:00
|
|
|
These patterns may be plain names or glob patterns like '2021*'
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_digest.add_argument(
|
|
|
|
'--glob_filenames',
|
|
|
|
'--glob-filenames',
|
|
|
|
metavar='pattern',
|
|
|
|
nargs='+',
|
|
|
|
default=None,
|
|
|
|
help='''
|
|
|
|
Only filenames matching any of these patterns will be digested.
|
2021-02-03 20:12:47 +00:00
|
|
|
These patterns may be plain names or glob patterns like '*.jpg'
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_digest.add_argument(
|
|
|
|
'--no_albums',
|
|
|
|
'--no-albums',
|
|
|
|
dest='make_albums',
|
|
|
|
action='store_false',
|
|
|
|
default=True,
|
|
|
|
help='''
|
2021-02-03 20:12:47 +00:00
|
|
|
Do not create any albums. By default, albums are created and nested to
|
|
|
|
match the directory structure.
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_digest.add_argument(
|
|
|
|
'--ratelimit',
|
|
|
|
dest='ratelimit',
|
|
|
|
type=float,
|
|
|
|
default=0.2,
|
|
|
|
help='''
|
|
|
|
Limit the ingest of new Photos to only one per this many seconds. This
|
|
|
|
can be used to reduce system load or to make sure that two photos don't
|
|
|
|
get the same `created` timestamp.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_digest.add_argument(
|
|
|
|
'--no_recurse',
|
|
|
|
'--no-recurse',
|
|
|
|
dest='recurse',
|
|
|
|
action='store_false',
|
|
|
|
default=True,
|
|
|
|
help='''
|
|
|
|
Do not recurse into subdirectories. Only create Photos from files in
|
|
|
|
the current directory. By default, we traverse all subdirectories except
|
|
|
|
those excluded by --exclude-directories.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_digest.add_argument(
|
|
|
|
'--hash_bytes_per_second',
|
|
|
|
'--hash-bytes-per-second',
|
|
|
|
metavar='bytes',
|
|
|
|
default=None,
|
|
|
|
help='''
|
|
|
|
Limit the speed of file hashing. This can be used to reduce system load.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_digest.add_argument(
|
|
|
|
'--yes',
|
|
|
|
dest='autoyes',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
|
|
|
Commit the database without prompting.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_digest.set_defaults(func=digest_directory_argparse)
|
2020-12-30 23:47:35 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
################################################################################################
|
2020-12-30 23:47:35 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
p_easybake = subparsers.add_parser(
|
|
|
|
'easybake',
|
|
|
|
description='''
|
|
|
|
Create and manipulate tags by easybake strings.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_easybake.examples = [
|
|
|
|
'people.family.parents.mother+mom',
|
|
|
|
'watchlist=to_watch',
|
|
|
|
]
|
|
|
|
p_easybake.add_argument(
|
|
|
|
'eb_strings',
|
|
|
|
nargs='+',
|
|
|
|
help='''
|
|
|
|
One or more easybake strings. Easybake strings work like this:
|
|
|
|
Every tag name is implicitly created if it does not already exist.
|
|
|
|
Dot '.' is used to make hierarchies.
|
|
|
|
Plus '+' is used to make synonyms.
|
|
|
|
Equals '=' is used to rename tags.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_easybake.add_argument(
|
|
|
|
'--yes',
|
|
|
|
dest='autoyes',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
|
|
|
Commit the database without prompting.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_easybake.set_defaults(func=easybake_argparse)
|
2020-12-30 23:47:35 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
################################################################################################
|
|
|
|
|
|
|
|
p_export_symlinks = subparsers.add_parser(
|
|
|
|
'export_symlinks',
|
|
|
|
aliases=['export-symlinks'],
|
|
|
|
description='''
|
|
|
|
Search for photos or albums, then create symlinks pointing to the results.
|
|
|
|
|
|
|
|
THIS IS STILL A BIT EXPERIMENTAL.
|
|
|
|
This can be used to gather up search results for the purpose of further
|
|
|
|
uploading, transfering, etc. with other applications.
|
|
|
|
Symlinks point to files (if result is a photo) or directories (if result is
|
|
|
|
an album with an associated directory).
|
|
|
|
Albums are limited to only one associated directory since the output
|
|
|
|
symlink can't point to two places at once.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_export_symlinks.add_argument(
|
|
|
|
'--destination',
|
|
|
|
dest='destination',
|
|
|
|
required=True,
|
|
|
|
help='''
|
|
|
|
A path to a directory into which the symlinks will be placed.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_export_symlinks.add_argument(
|
|
|
|
'--dry',
|
|
|
|
dest='dry_run',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
2020-12-30 23:47:35 +00:00
|
|
|
Print the results without actually creating the symlinks.
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_export_symlinks.add_argument(
|
|
|
|
'--prune',
|
|
|
|
dest='prune',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
2020-12-30 23:47:35 +00:00
|
|
|
In the destination directory, any existing symlinks whose target no
|
|
|
|
longer exists will be deleted.
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_export_symlinks.add_argument(
|
|
|
|
'--photos',
|
|
|
|
dest='photo_id_args',
|
|
|
|
metavar='photo_id',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will be treated as IDs of Photos to export.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_export_symlinks.add_argument(
|
|
|
|
'--photo_search',
|
|
|
|
'--photo-search',
|
|
|
|
dest='photo_search_args',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will go to the search command to generate the
|
|
|
|
list of Photos to export. See search --help for help.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_export_symlinks.add_argument(
|
|
|
|
'--albums',
|
|
|
|
dest='album_id_args',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will be treated as IDs of Albums to export.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_export_symlinks.add_argument(
|
|
|
|
'--album_search',
|
|
|
|
'--album-search',
|
|
|
|
dest='album_search_args',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will go to the search command to generate the
|
|
|
|
list of Albums to export. See search --help for help.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_export_symlinks.set_defaults(func=export_symlinks_argparse)
|
2020-12-30 23:47:35 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
################################################################################################
|
2020-12-30 23:47:35 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
p_generate_thumbnail = subparsers.add_parser(
|
|
|
|
'generate_thumbnail',
|
|
|
|
aliases=['generate-thumbnail'],
|
|
|
|
description='''
|
|
|
|
Generate thumbnails for photos.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_generate_thumbnail.examples = [
|
|
|
|
'--photo-search --has-thumbnail no',
|
|
|
|
]
|
|
|
|
p_generate_thumbnail.add_argument(
|
|
|
|
'globs',
|
|
|
|
nargs='*',
|
|
|
|
help='''
|
|
|
|
Select Photos by using glob patterns that match files.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_generate_thumbnail.add_argument(
|
|
|
|
'--photos',
|
|
|
|
dest='photo_id_args',
|
|
|
|
metavar='photo_id',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will be treated as IDs of Photos to thumbnail.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_generate_thumbnail.add_argument(
|
|
|
|
'--photo_search',
|
|
|
|
'--photo-search',
|
|
|
|
dest='photo_search_args',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will go to the search command to generate the
|
|
|
|
list of Photos to thumbnail. See search --help for help.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_generate_thumbnail.add_argument(
|
|
|
|
'--yes',
|
|
|
|
dest='autoyes',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
|
|
|
Commit the database without prompting.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_generate_thumbnail.set_defaults(func=generate_thumbnail_argparse)
|
2020-12-30 23:47:35 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
################################################################################################
|
2021-05-19 06:43:18 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
p_init = subparsers.add_parser(
|
|
|
|
'init',
|
|
|
|
aliases=['create'],
|
|
|
|
description='''
|
|
|
|
Create a new Etiquette database in the current directory.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_init.set_defaults(func=init_argparse)
|
2020-12-30 23:47:35 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
################################################################################################
|
2020-12-30 23:47:35 +00:00
|
|
|
|
2022-09-01 03:21:07 +00:00
|
|
|
p_new_photo = subparsers.add_parser(
|
|
|
|
'new_photo',
|
|
|
|
aliases=['new-photo', 'new_photos', 'new-photos'],
|
|
|
|
description='''
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_new_photo.add_argument(
|
|
|
|
'globs',
|
|
|
|
nargs='+',
|
|
|
|
help='''
|
|
|
|
Make Photos from files that match glob patterns.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_new_photo.add_argument(
|
|
|
|
'--ratelimit',
|
|
|
|
type=float,
|
|
|
|
default=0.25,
|
|
|
|
help='''
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_new_photo.add_argument(
|
|
|
|
'--make_album', '--make-album',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_new_photo.add_argument(
|
|
|
|
'--album_title', '--album-title',
|
|
|
|
default=None,
|
|
|
|
help='''
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_new_photo.add_argument(
|
|
|
|
'--yes',
|
|
|
|
dest='autoyes',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
|
|
|
Commit the database without prompting.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_new_photo.set_defaults(func=new_photo_argparse)
|
|
|
|
|
|
|
|
################################################################################################
|
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
p_purge_deleted_files = subparsers.add_parser(
|
|
|
|
'purge_deleted_files',
|
|
|
|
aliases=['purge-deleted-files'],
|
|
|
|
description='''
|
|
|
|
Delete any Photo objects whose file no longer exists on disk.
|
2020-12-30 23:47:35 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
When --photos and --photo-search are not passed, all photos under the cwd
|
|
|
|
and subdirectories will be checked.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_purge_deleted_files.add_argument(
|
|
|
|
'--photos',
|
|
|
|
dest='photo_id_args',
|
|
|
|
metavar='photo_id',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will be treated as IDs of Photos to purge,
|
|
|
|
if eligible.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_purge_deleted_files.add_argument(
|
|
|
|
'--photo_search',
|
|
|
|
'--photo-search',
|
|
|
|
dest='photo_search_args',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will go to the search command to generate the
|
|
|
|
list of Photos to purge, if eligible. See search --help for help.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_purge_deleted_files.add_argument(
|
|
|
|
'--yes',
|
|
|
|
dest='autoyes',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
|
|
|
Commit the database without prompting.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_purge_deleted_files.set_defaults(func=purge_deleted_files_argparse)
|
2021-02-03 20:12:47 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
################################################################################################
|
2021-02-03 20:12:47 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
p_purge_empty_albums = subparsers.add_parser(
|
|
|
|
'purge_empty_albums',
|
|
|
|
aliases=['purge-empty-albums'],
|
|
|
|
description='''
|
|
|
|
Delete any albums which have no child albums or photos.
|
2021-02-03 20:12:47 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
Consider running purge_deleted_files first, so that albums containing
|
|
|
|
deleted files will get cleared out and then caught by this function.
|
2021-02-03 20:12:47 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
With no args, all albums will be checked.
|
|
|
|
Or you can pass specific album ids. (--album-search is not available since
|
|
|
|
albums only appear in search results when a matching photo is found, and
|
|
|
|
we're looking for albums with no photos!)
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_purge_empty_albums.add_argument(
|
|
|
|
'--albums',
|
|
|
|
dest='album_id_args',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will be treated as IDs of Albums to purge,
|
|
|
|
if eligible.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_purge_empty_albums.add_argument(
|
|
|
|
'--yes',
|
|
|
|
dest='autoyes',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
|
|
|
Commit the database without prompting.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_purge_empty_albums.set_defaults(func=purge_empty_albums_argparse)
|
2021-02-03 20:12:47 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
################################################################################################
|
2021-02-03 20:12:47 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
p_reload_metadata = subparsers.add_parser(
|
|
|
|
'reload_metadata',
|
|
|
|
aliases=['reload-metadata'],
|
|
|
|
description='''
|
|
|
|
Reload photos' metadata by reading the files from disk.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_reload_metadata.add_argument(
|
|
|
|
'globs',
|
|
|
|
nargs='*',
|
|
|
|
help='''
|
|
|
|
Select Photos by using glob patterns that match files.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_reload_metadata.add_argument(
|
|
|
|
'--hash_bytes_per_second',
|
|
|
|
'--hash-bytes-per-second',
|
|
|
|
default=None,
|
|
|
|
help='''
|
2021-02-03 20:12:47 +00:00
|
|
|
A string like "10mb" to limit the speed of file hashing for the purpose
|
|
|
|
of reducing system load.
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_reload_metadata.add_argument(
|
|
|
|
'--force',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
|
|
|
By default, we wil skip any files that have the same mtime and byte
|
|
|
|
size as before. You can pass --force to always reload.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_reload_metadata.add_argument(
|
|
|
|
'--photos',
|
|
|
|
dest='photo_id_args',
|
|
|
|
metavar='photo_id',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will be treated as IDs of Photos to reload.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_reload_metadata.add_argument(
|
|
|
|
'--photo_search',
|
|
|
|
'--photo-search',
|
|
|
|
dest='photo_search_args',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will go to the search command to generate the
|
|
|
|
list of Photos to reload. See search --help for help.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_reload_metadata.add_argument(
|
|
|
|
'--yes',
|
|
|
|
dest='autoyes',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
|
|
|
Commit the database without prompting.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_reload_metadata.set_defaults(func=reload_metadata_argparse)
|
2021-05-19 06:43:18 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
################################################################################################
|
2021-01-03 10:58:18 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
p_relocate = subparsers.add_parser(
|
|
|
|
'relocate',
|
|
|
|
description='''
|
|
|
|
Update a photo's filepath in the database. This does not actually move
|
|
|
|
the file there, rather it is used for updating photos that have already
|
|
|
|
been moved by external tools.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_relocate.add_argument(
|
|
|
|
'photo_id',
|
|
|
|
)
|
|
|
|
p_relocate.add_argument(
|
|
|
|
'filepath',
|
|
|
|
)
|
|
|
|
p_relocate.add_argument(
|
|
|
|
'--yes',
|
|
|
|
dest='autoyes',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
|
|
|
Commit the database without prompting.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_relocate.set_defaults(func=relocate_argparse)
|
2020-12-30 23:47:35 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
################################################################################################
|
2020-12-30 23:47:35 +00:00
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
p_search = subparsers.add_parser(
|
|
|
|
'search',
|
|
|
|
description='''
|
|
|
|
Search for photos and albums with complex operators. Many other commands
|
|
|
|
can use search arguments to pick which Photos / Albums to process.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--area',
|
2022-08-14 20:20:05 +00:00
|
|
|
metavar='X..Y',
|
2022-02-13 03:56:00 +00:00
|
|
|
default=None,
|
|
|
|
help='''
|
2020-12-30 23:47:35 +00:00
|
|
|
Photo/video width*height between X and Y.
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--width',
|
2022-08-14 20:20:05 +00:00
|
|
|
metavar='X..Y',
|
2022-02-13 03:56:00 +00:00
|
|
|
default=None,
|
|
|
|
help='''
|
2020-12-30 23:47:35 +00:00
|
|
|
Photo/video width between X and Y.
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--height',
|
2022-08-14 20:20:05 +00:00
|
|
|
metavar='X..Y',
|
2022-02-13 03:56:00 +00:00
|
|
|
default=None,
|
|
|
|
help='''
|
2020-12-30 23:47:35 +00:00
|
|
|
Photo/video height between X and Y.
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
2022-08-14 01:08:45 +00:00
|
|
|
'--aspectratio',
|
2022-08-14 20:20:05 +00:00
|
|
|
metavar='X..Y',
|
2022-02-13 03:56:00 +00:00
|
|
|
default=None,
|
|
|
|
help='''
|
2020-12-30 23:47:35 +00:00
|
|
|
Photo/video aspect ratio between X and Y.
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--bytes',
|
2022-08-14 20:20:05 +00:00
|
|
|
metavar='X..Y',
|
2022-02-13 03:56:00 +00:00
|
|
|
default=None,
|
|
|
|
help='''
|
2020-12-30 23:47:35 +00:00
|
|
|
File size in bytes between X and Y.
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--duration',
|
2022-08-14 20:20:05 +00:00
|
|
|
metavar='X..Y',
|
2022-02-13 03:56:00 +00:00
|
|
|
default=None,
|
|
|
|
help='''
|
2020-12-30 23:47:35 +00:00
|
|
|
Media duration between X and Y seconds.
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--author',
|
|
|
|
metavar='A,B,C',
|
|
|
|
default=None,
|
|
|
|
help='''
|
|
|
|
Photo authored by user A, B, or C...
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--created',
|
2022-08-14 20:20:05 +00:00
|
|
|
metavar='X..Y',
|
2022-02-13 03:56:00 +00:00
|
|
|
default=None,
|
|
|
|
help='''
|
2020-12-30 23:47:35 +00:00
|
|
|
Photo creation date between X and Y unix timestamp.
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--extension',
|
|
|
|
metavar='A,B,C',
|
|
|
|
default=None,
|
|
|
|
help='''
|
2020-12-30 23:47:35 +00:00
|
|
|
Photo with any extension of A, B, C...
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--extension_not',
|
|
|
|
'--extension-not',
|
|
|
|
metavar='A,B,C',
|
|
|
|
default=None,
|
|
|
|
help='''
|
2020-12-30 23:47:35 +00:00
|
|
|
Photo without any extension of A, B, C...
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--filename',
|
|
|
|
default=None,
|
|
|
|
help='''
|
|
|
|
Search for strings within Photos' filenames.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--has_tags',
|
|
|
|
'--has-tags',
|
|
|
|
default=None,
|
|
|
|
help='''
|
|
|
|
If "yes", Photo must have at least one tag.
|
|
|
|
If "no", Photo must have no tags.
|
|
|
|
If "null", doesn't matter.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--has_thumbnail',
|
|
|
|
'--has-thumbnail',
|
|
|
|
default=None,
|
|
|
|
help='''
|
|
|
|
If "yes", Photo must have a thumbnail.
|
|
|
|
If "no", Photo must not have a thumbnail.
|
|
|
|
If "null", doesn't matter.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--is_searchhidden',
|
|
|
|
'--is-searchhidden',
|
|
|
|
default=False,
|
|
|
|
help='''
|
|
|
|
If "yes", Photo must be searchhidden.
|
|
|
|
If "no", Photo must not be searchhidden.
|
|
|
|
If "null", doesn't matter.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--sha256',
|
|
|
|
metavar='A,B,C',
|
|
|
|
default=None,
|
|
|
|
help='''
|
2021-02-03 20:12:47 +00:00
|
|
|
Photo with any sha256 of A, B, C...
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--mimetype',
|
|
|
|
metavar='A,B,C',
|
|
|
|
default=None,
|
|
|
|
help='''
|
|
|
|
Photo with any mimetype of A, B, C...
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--tag_musts',
|
|
|
|
'--tag-musts',
|
|
|
|
metavar='A,B,C',
|
|
|
|
default=None,
|
|
|
|
help='''
|
2020-12-30 23:47:35 +00:00
|
|
|
Photo must have all tags A and B and C...
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--tag_mays',
|
|
|
|
'--tag-mays',
|
|
|
|
metavar='A,B,C',
|
|
|
|
default=None,
|
|
|
|
help='''
|
2020-12-30 23:47:35 +00:00
|
|
|
Photo must have at least one tag of A, B, C...
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--tag_forbids',
|
|
|
|
'--tag-forbids',
|
|
|
|
metavar='A,B,C',
|
|
|
|
default=None,
|
|
|
|
help='''
|
2020-12-30 23:47:35 +00:00
|
|
|
Photo must not have any tags of A, B, C...
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--tag_expression',
|
|
|
|
'--tag-expression',
|
|
|
|
default=None,
|
|
|
|
help='''
|
2020-12-30 23:47:35 +00:00
|
|
|
Complex expression string to match tags.
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--limit',
|
|
|
|
default=None,
|
|
|
|
help='''
|
2020-12-30 23:47:35 +00:00
|
|
|
Limit results to first X items.
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--offset',
|
|
|
|
default=None,
|
|
|
|
help='''
|
2020-12-30 23:47:35 +00:00
|
|
|
Skip the first X items.
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--orderby',
|
|
|
|
dest='orderby',
|
|
|
|
default='basename-ASC',
|
|
|
|
help='''
|
2020-12-30 23:47:35 +00:00
|
|
|
Order the results by property X in direction Y. E.g. created-desc or
|
|
|
|
bytes-asc.
|
2022-02-13 03:56:00 +00:00
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_search.add_argument(
|
|
|
|
'--album_search',
|
|
|
|
'--album-search',
|
|
|
|
dest='album_search_args',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
Search for albums instead of photos.
|
|
|
|
''',
|
|
|
|
)
|
2020-09-27 17:51:28 +00:00
|
|
|
p_search.set_defaults(func=search_argparse)
|
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
################################################################################################
|
|
|
|
|
|
|
|
p_show_associated_directories = subparsers.add_parser(
|
|
|
|
'show_associated_directories',
|
|
|
|
aliases=['show-associated-directories'],
|
|
|
|
description='''
|
|
|
|
Show the associated directories for albums.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_show_associated_directories.add_argument(
|
|
|
|
'--albums',
|
|
|
|
dest='album_id_args',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will be treated as IDs of Albums to list.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_show_associated_directories.add_argument(
|
|
|
|
'--album_search',
|
|
|
|
'--album-search',
|
|
|
|
dest='album_search_args',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will go to the search command to generate the
|
|
|
|
list of Albums to list. See search --help for help.
|
|
|
|
''',
|
|
|
|
)
|
2020-11-16 01:49:28 +00:00
|
|
|
p_show_associated_directories.set_defaults(func=show_associated_directories_argparse)
|
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
################################################################################################
|
|
|
|
|
|
|
|
p_set_searchhidden = subparsers.add_parser(
|
|
|
|
'set_searchhidden',
|
|
|
|
aliases=['set-searchhidden'],
|
|
|
|
description='''
|
|
|
|
Mark photos as searchhidden.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_set_searchhidden.add_argument(
|
|
|
|
'--photos',
|
|
|
|
dest='photo_id_args',
|
|
|
|
metavar='photo_id',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will be treated as IDs of Photos to set.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_set_searchhidden.add_argument(
|
|
|
|
'--photo_search',
|
|
|
|
'--photo-search',
|
|
|
|
dest='photo_search_args',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will go to the search command to generate the
|
|
|
|
list of Photos to set. See search --help for help.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_set_searchhidden.add_argument(
|
|
|
|
'--yes',
|
|
|
|
dest='autoyes',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
|
|
|
Commit the database without prompting.
|
|
|
|
''',
|
|
|
|
)
|
2020-11-03 08:07:29 +00:00
|
|
|
p_set_searchhidden.set_defaults(func=lambda args: set_unset_searchhidden_argparse(args, searchhidden=True))
|
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
################################################################################################
|
|
|
|
|
|
|
|
p_unset_searchhidden = subparsers.add_parser(
|
|
|
|
'unset_searchhidden',
|
|
|
|
aliases=['unset-searchhidden'],
|
|
|
|
description='''
|
|
|
|
Unmark photos as searchhidden.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_unset_searchhidden.add_argument(
|
|
|
|
'--photos',
|
|
|
|
dest='photo_id_args',
|
|
|
|
metavar='photo_id',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will be treated as IDs of Photos to unset.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_unset_searchhidden.add_argument(
|
|
|
|
'--photo_search',
|
|
|
|
'--photo-search',
|
|
|
|
dest='photo_search_args',
|
|
|
|
nargs='...',
|
|
|
|
help='''
|
|
|
|
All remaining arguments will go to the search command to generate the
|
|
|
|
list of Photos to unset. See search --help for help.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_unset_searchhidden.add_argument(
|
|
|
|
'--yes',
|
|
|
|
dest='autoyes',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
|
|
|
Commit the database without prompting.
|
|
|
|
''',
|
|
|
|
)
|
2020-11-03 08:07:29 +00:00
|
|
|
p_unset_searchhidden.set_defaults(func=lambda args: set_unset_searchhidden_argparse(args, searchhidden=False))
|
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
################################################################################################
|
|
|
|
|
|
|
|
p_tag_breplace = subparsers.add_parser(
|
|
|
|
'tag_breplace',
|
|
|
|
aliases=['tag-breplace'],
|
|
|
|
description='''
|
|
|
|
For all tags in the database, use find-and-replace to rename the tags.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_tag_breplace.add_argument(
|
|
|
|
'replace_from',
|
|
|
|
)
|
|
|
|
p_tag_breplace.add_argument(
|
|
|
|
'replace_to',
|
|
|
|
)
|
|
|
|
p_tag_breplace.add_argument(
|
|
|
|
'--set_synonym',
|
|
|
|
'--set-synonym',
|
|
|
|
dest='set_synonym',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
|
|
|
After renaming the tag, assign the old name as a synonym to the new one.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_tag_breplace.add_argument(
|
|
|
|
'--regex',
|
|
|
|
dest='regex',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
|
|
|
Treat replace_from and replace_to as regex patterns instead of plain
|
|
|
|
strings.
|
|
|
|
''',
|
|
|
|
)
|
|
|
|
p_tag_breplace.add_argument(
|
|
|
|
'--yes',
|
|
|
|
dest='autoyes',
|
|
|
|
action='store_true',
|
|
|
|
help='''
|
|
|
|
Commit the database without prompting.
|
|
|
|
''',
|
|
|
|
)
|
2020-10-26 03:21:27 +00:00
|
|
|
p_tag_breplace.set_defaults(func=tag_breplace_argparse)
|
|
|
|
|
2022-02-13 03:56:00 +00:00
|
|
|
################################################################################################
|
|
|
|
|
|
|
|
p_tag_list = subparsers.add_parser(
|
|
|
|
'tag_list',
|
|
|
|
aliases=['tag-list'],
|
|
|
|
description='''
|
|
|
|
Show all tags in the database.
|
|
|
|
''',
|
|
|
|
)
|
2021-05-16 01:20:59 +00:00
|
|
|
p_tag_list.set_defaults(func=tag_list_argparse)
|
|
|
|
|
2020-11-03 08:07:29 +00:00
|
|
|
##
|
|
|
|
|
2021-09-30 21:43:45 +00:00
|
|
|
def postprocessor(args):
|
2022-07-16 06:00:07 +00:00
|
|
|
if getattr(args, 'photo_search_args', None) is not None:
|
2022-02-13 03:56:00 +00:00
|
|
|
args.photo_search_args = p_search.parse_args(args.photo_search_args)
|
|
|
|
else:
|
|
|
|
args.photo_search_args = None
|
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if getattr(args, 'album_search_args', None) is not None:
|
2022-02-13 03:56:00 +00:00
|
|
|
args.album_search_args = p_search.parse_args(args.album_search_args)
|
|
|
|
else:
|
|
|
|
args.album_search_args = None
|
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if getattr(args, 'photo_id_args', None) is not None:
|
2022-02-13 03:56:00 +00:00
|
|
|
args.photo_id_args = [
|
|
|
|
photo_id
|
|
|
|
for arg in args.photo_id_args
|
|
|
|
for photo_id in stringtools.comma_space_split(arg)
|
|
|
|
]
|
|
|
|
else:
|
|
|
|
args.photo_id_args = None
|
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if getattr(args, 'album_id_args', None) is not None:
|
2022-02-13 03:56:00 +00:00
|
|
|
args.album_id_args = [
|
|
|
|
album_id
|
|
|
|
for arg in args.album_id_args
|
|
|
|
for album_id in stringtools.comma_space_split(arg)
|
|
|
|
]
|
|
|
|
else:
|
|
|
|
args.album_id_args = None
|
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if not getattr(args, 'globs', None) is not None:
|
2022-01-11 01:53:00 +00:00
|
|
|
args.globs = None
|
|
|
|
|
2022-07-16 06:00:07 +00:00
|
|
|
if not getattr(args, 'glob', None) is not None:
|
2022-01-11 01:53:00 +00:00
|
|
|
args.glob = None
|
|
|
|
|
|
|
|
args.any_photo_args = bool(
|
2020-12-30 23:47:35 +00:00
|
|
|
args.photo_search_args or
|
|
|
|
args.photo_id_args or
|
2022-01-11 01:53:00 +00:00
|
|
|
args.globs or
|
|
|
|
args.glob
|
|
|
|
)
|
|
|
|
args.any_album_args = bool(
|
|
|
|
args.album_id_args or
|
|
|
|
args.album_search_args
|
2020-12-30 23:47:35 +00:00
|
|
|
)
|
|
|
|
return args
|
|
|
|
|
2021-01-09 23:41:52 +00:00
|
|
|
try:
|
2022-07-16 06:00:07 +00:00
|
|
|
return betterhelp.go(parser, argv, args_postprocessor=postprocessor)
|
2021-01-09 23:41:52 +00:00
|
|
|
except etiquette.exceptions.NoClosestPhotoDB as exc:
|
|
|
|
pipeable.stderr(exc.error_message)
|
2021-01-15 08:02:24 +00:00
|
|
|
pipeable.stderr('Try `etiquette_cli.py init` to create the database.')
|
2021-01-09 23:41:52 +00:00
|
|
|
return 1
|
2020-09-27 17:51:28 +00:00
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
raise SystemExit(main(sys.argv[1:]))
|