ordigi/ordigi.py

482 lines
12 KiB
Python
Raw Normal View History

#!/usr/bin/env python
import os
import re
import sys
import click
from ordigi.config import Config
2021-08-14 21:31:37 +02:00
from ordigi import constants
from ordigi import log
2021-08-27 12:45:25 +02:00
from ordigi.collection import Collection
from ordigi.geolocation import GeoLocation
2021-09-18 22:06:34 +02:00
_logger_options = [
click.option(
'--debug',
default=False,
is_flag=True,
help='Override the value in constants.py with True.',
),
click.option(
'--verbose',
'-v',
default=False,
is_flag=True,
help='True if you want to see details of file processing',
),
2021-09-18 22:06:34 +02:00
]
_dry_run_options = [
click.option(
'--dry-run',
default=False,
is_flag=True,
help='Dry run only, no change made to the filesystem.',
)
2021-09-18 22:06:34 +02:00
]
_filter_option = [
click.option(
'--exclude',
'-e',
default=set(),
multiple=True,
help='Directories or files to exclude.',
),
click.option(
'--filter-by-ext',
'-f',
default=set(),
multiple=True,
help="""Use filename
2021-09-18 22:06:34 +02:00
extension to filter files for sorting. If value is '*', use
common media file extension for filtering. Ignored files remain in
the same directory structure""",
),
click.option('--glob', '-g', default='**/*', help='Glob file selection'),
2021-09-18 22:06:34 +02:00
]
def print_help(command):
click.echo(command.get_help(click.Context(sort)))
2021-09-18 22:06:34 +02:00
def add_options(options):
def _add_options(func):
for option in reversed(options):
func = option(func)
return func
2021-09-18 22:06:34 +02:00
return _add_options
def _get_exclude(opt, exclude):
# if no exclude list was passed in we check if there's a config
if len(exclude) == 0:
exclude = opt['exclude']
return set(exclude)
@click.command('sort')
2021-09-18 22:06:34 +02:00
@add_options(_logger_options)
@add_options(_dry_run_options)
@add_options(_filter_option)
@click.option(
'--album-from-folder',
default=False,
is_flag=True,
help="Use images' folders as their album names.",
)
@click.option(
'--destination',
'-d',
type=click.Path(file_okay=False),
default=None,
help='Sort files into this directory.',
)
@click.option('--clean', '-C', default=False, is_flag=True, help='Clean empty folders')
@click.option(
'--copy',
'-c',
default=False,
is_flag=True,
help='True if you want files to be copied over from src_dir to\
dest_dir rather than moved',
)
@click.option(
'--ignore-tags',
'-I',
default=set(),
multiple=True,
help='Specific tags or group that will be ignored when\
searching for file data. Example \'File:FileModifyDate\' or \'Filename\'',
)
@click.option(
'--interactive', '-i', default=False, is_flag=True, help="Interactive mode"
)
@click.option(
'--path-format',
'-p',
default=None,
help='set custom featured path format',
)
@click.option(
'--remove-duplicates',
'-R',
default=False,
is_flag=True,
help='True to remove files that are exactly the same in name\
and a file hash',
)
@click.option(
'--reset-cache',
'-r',
default=False,
is_flag=True,
help='Regenerate the hash.json and location.json database ',
)
@click.option(
'--use-date-filename',
'-f',
default=False,
is_flag=True,
help="Use filename date for media original date.",
)
@click.option(
'--use-file-dates',
'-F',
default=False,
is_flag=True,
help="Use file date created or modified for media original date.",
)
@click.argument('paths', required=True, nargs=-1, type=click.Path())
2021-09-18 22:06:34 +02:00
def sort(**kwargs):
"""Sort files or directories by reading their EXIF and organizing them
2021-08-13 21:11:24 +02:00
according to ordigi.conf preferences.
"""
2021-09-18 22:06:34 +02:00
destination = kwargs['destination']
2021-10-16 19:29:52 +02:00
log_level = log.level(kwargs['verbose'], kwargs['debug'])
2021-09-18 22:06:34 +02:00
paths = kwargs['paths']
if kwargs['copy']:
mode = 'copy'
else:
mode = 'move'
2021-10-16 19:29:52 +02:00
logger = log.get_logger(level=log_level)
cache = True
2021-09-18 22:06:34 +02:00
if kwargs['reset_cache']:
cache = False
2021-08-27 12:45:25 +02:00
if len(paths) > 1:
if not destination:
# Use last path argument as destination
destination = paths[-1]
paths = paths[0:-1]
elif paths:
# Source and destination are the same
destination = paths[0]
else:
2021-08-27 12:45:25 +02:00
logger.error(f'`ordigi sort` need at least one path argument')
sys.exit(1)
paths = set(paths)
2021-08-14 21:37:43 +02:00
config = Config(constants.CONFIG_FILE)
opt = config.get_options()
path_format = opt['path_format']
if kwargs['path_format']:
path_format = kwargs['path_format']
2021-09-18 22:06:34 +02:00
exclude = _get_exclude(opt, kwargs['exclude'])
filter_by_ext = set(kwargs['filter_by_ext'])
collection = Collection(
destination,
path_format,
kwargs['album_from_folder'],
cache,
opt['day_begins'],
kwargs['dry_run'],
exclude,
filter_by_ext,
kwargs['glob'],
kwargs['interactive'],
logger,
opt['max_deep'],
mode,
kwargs['use_date_filename'],
kwargs['use_file_dates'],
)
2021-10-16 19:29:52 +02:00
loc = GeoLocation(opt['geocoder'], logger, opt['prefer_english_names'], opt['timeout'])
summary, result = collection.sort_files(
paths, loc, kwargs['remove_duplicates'], kwargs['ignore_tags']
)
2021-09-18 22:06:34 +02:00
if kwargs['clean']:
2021-10-16 19:29:52 +02:00
collection.remove_empty_folders(destination)
2021-08-14 21:37:43 +02:00
2021-10-16 19:29:52 +02:00
if log_level < 30:
2021-09-29 07:36:47 +02:00
summary.print()
2021-09-29 07:36:47 +02:00
if not result:
sys.exit(1)
2021-08-14 21:37:43 +02:00
@click.command('clean')
2021-09-18 22:06:34 +02:00
@add_options(_logger_options)
@add_options(_dry_run_options)
@add_options(_filter_option)
@click.option(
'--dedup-regex',
'-d',
default=set(),
multiple=True,
help='Regex to match duplicate strings parts',
)
@click.option(
'--delete-excluded', '-d', default=False, is_flag=True, help='Remove excluded files'
)
@click.option(
'--folders', '-f', default=False, is_flag=True, help='Remove empty folders'
)
@click.option(
'--path-string', '-p', default=False, is_flag=True, help='Deduplicate path string'
)
@click.option(
'--remove-duplicates',
'-R',
default=False,
is_flag=True,
help='True to remove files that are exactly the same in name and a file hash',
)
@click.option(
'--root',
'-r',
type=click.Path(file_okay=False),
default=None,
help='Root dir of media collection. If not set, use path',
)
2021-08-14 21:37:43 +02:00
@click.argument('path', required=True, nargs=1, type=click.Path())
2021-09-18 22:06:34 +02:00
def clean(**kwargs):
2021-08-14 21:37:43 +02:00
"""Remove empty folders
Usage: clean [--verbose|--debug] directory [removeRoot]"""
import ipdb; ipdb.set_trace()
result = True
2021-09-18 22:06:34 +02:00
dry_run = kwargs['dry_run']
folders = kwargs['folders']
2021-10-16 19:29:52 +02:00
log_level = log.level(kwargs['verbose'], kwargs['debug'])
2021-09-18 22:06:34 +02:00
root = kwargs['root']
path = kwargs['path']
2021-08-14 21:37:43 +02:00
2021-10-16 19:29:52 +02:00
logger = log.get_logger(level=log_level)
2021-08-27 12:45:25 +02:00
clean_all = False
if not folders:
clean_all = True
if not root:
root = path
2021-08-14 21:37:43 +02:00
2021-08-27 12:45:25 +02:00
config = Config(constants.CONFIG_FILE)
opt = config.get_options()
2021-09-18 22:06:34 +02:00
exclude = _get_exclude(opt, kwargs['exclude'])
filter_by_ext = set(kwargs['filter_by_ext'])
2021-10-16 19:29:52 +02:00
collection = Collection(
root,
opt['path_format'],
dry_run=dry_run,
exclude=exclude,
filter_by_ext=filter_by_ext,
glob=kwargs['glob'],
logger=logger,
max_deep=opt['max_deep'],
2021-10-16 19:29:52 +02:00
mode='move',
)
2021-09-18 22:06:34 +02:00
if kwargs['path_string']:
dedup_regex = list(kwargs['dedup_regex'])
summary, result = collection.dedup_regex(
path, dedup_regex, kwargs['remove_duplicates']
)
2021-08-31 16:18:41 +02:00
if clean_all or folders:
2021-10-16 19:29:52 +02:00
collection.remove_empty_folders(path)
2021-08-27 12:45:25 +02:00
if kwargs['delete_excluded']:
collection.remove_excluded_files()
2021-10-16 19:29:52 +02:00
if log_level < 30:
2021-09-29 07:36:47 +02:00
summary.print()
2021-08-27 12:45:25 +02:00
2021-09-29 07:36:47 +02:00
if not result:
2021-08-27 12:45:25 +02:00
sys.exit(1)
2021-08-14 21:37:43 +02:00
2021-10-15 06:41:22 +02:00
@click.command('init')
2021-09-18 22:06:34 +02:00
@add_options(_logger_options)
2021-10-15 06:41:22 +02:00
@click.argument('path', required=True, nargs=1, type=click.Path())
def init(**kwargs):
"""Regenerate the hash.json database which contains all of the sha256 signatures of media files."""
2021-10-15 06:41:22 +02:00
config = Config(constants.CONFIG_FILE)
opt = config.get_options()
2021-10-16 19:29:52 +02:00
log_level = log.level(kwargs['verbose'], kwargs['debug'])
logger = log.get_logger(level=log_level)
loc = GeoLocation(opt['geocoder'], logger, opt['prefer_english_names'], opt['timeout'])
collection = Collection(kwargs['path'], None, exclude=opt['exclude'], mode='move', logger=logger)
2021-10-15 06:41:22 +02:00
summary = collection.init(loc)
2021-10-16 19:29:52 +02:00
if log_level < 30:
2021-10-15 06:41:22 +02:00
summary.print()
2021-10-15 06:41:22 +02:00
@click.command('update')
2021-09-18 22:06:34 +02:00
@add_options(_logger_options)
2021-10-15 06:41:22 +02:00
@click.argument('path', required=True, nargs=1, type=click.Path())
def update(**kwargs):
"""Regenerate the hash.json database which contains all of the sha256 signatures of media files."""
2021-10-15 06:41:22 +02:00
config = Config(constants.CONFIG_FILE)
opt = config.get_options()
2021-10-16 19:29:52 +02:00
log_level = log.level(kwargs['verbose'], kwargs['debug'])
logger = log.get_logger(level=log_level)
loc = GeoLocation(opt['geocoder'], logger, opt['prefer_english_names'], opt['timeout'])
collection = Collection(kwargs['path'], None, exclude=opt['exclude'], mode='move', logger=logger)
2021-10-15 06:41:22 +02:00
summary = collection.update(loc)
2021-10-16 19:29:52 +02:00
if log_level < 30:
2021-10-15 06:41:22 +02:00
summary.print()
@click.command('check')
@add_options(_logger_options)
@click.argument('path', required=True, nargs=1, type=click.Path())
def check(**kwargs):
"""check db and verify hashes"""
2021-10-16 19:29:52 +02:00
log_level = log.level(kwargs['verbose'], kwargs['debug'])
logger = log.get_logger(level=log_level)
config = Config(constants.CONFIG_FILE)
opt = config.get_options()
collection = Collection(kwargs['path'], None, exclude=opt['exclude'], mode='move', logger=logger)
2021-10-15 06:41:22 +02:00
result = collection.check_db()
if result:
summary, result = collection.check_files()
2021-10-16 19:29:52 +02:00
if log_level < 30:
2021-10-15 06:41:22 +02:00
summary.print()
if not result:
sys.exit(1)
else:
logger.error('Db data is not accurate run `ordigi update`')
2021-10-15 06:41:22 +02:00
sys.exit(1)
@click.command('compare')
2021-09-18 22:06:34 +02:00
@add_options(_logger_options)
@add_options(_dry_run_options)
@add_options(_filter_option)
@click.option('--find-duplicates', '-f', default=False, is_flag=True)
@click.option(
'--output-dir',
'-o',
default=False,
is_flag=True,
help='output dir',
)
@click.option('--remove-duplicates', '-r', default=False, is_flag=True)
@click.option(
'--revert-compare',
'-R',
default=False,
is_flag=True,
help='Revert compare',
)
@click.option(
'--root',
'-r',
type=click.Path(file_okay=False),
default=None,
help='Root dir of media collection. If not set, use path',
)
@click.option(
'--similar-to',
'-s',
default=False,
help='Similar to given image',
)
@click.option(
'--similarity',
'-S',
default=80,
help='Similarity level for images',
)
@click.argument('path', nargs=1, required=True)
2021-09-18 22:06:34 +02:00
def compare(**kwargs):
'''Compare files in directories'''
2021-09-18 22:06:34 +02:00
dry_run = kwargs['dry_run']
2021-10-16 19:29:52 +02:00
log_level = log.level(kwargs['verbose'], kwargs['debug'])
2021-09-18 22:06:34 +02:00
root = kwargs['root']
path = kwargs['path']
2021-10-16 19:29:52 +02:00
logger = log.get_logger(level=log_level)
2021-08-27 12:45:25 +02:00
if not root:
2021-09-18 22:06:34 +02:00
root = kwargs['path']
2021-08-27 12:45:25 +02:00
config = Config(constants.CONFIG_FILE)
opt = config.get_options()
2021-09-18 22:06:34 +02:00
exclude = _get_exclude(opt, kwargs['exclude'])
filter_by_ext = set(kwargs['filter_by_ext'])
collection = Collection(
root,
None,
exclude=exclude,
filter_by_ext=filter_by_ext,
glob=kwargs['glob'],
mode='move',
dry_run=dry_run,
logger=logger,
)
2021-09-18 22:06:34 +02:00
if kwargs['revert_compare']:
summary, result = collection.revert_compare(path)
else:
summary, result = collection.sort_similar_images(path, kwargs['similarity'])
2021-10-16 19:29:52 +02:00
if log_level < 30:
2021-09-29 07:36:47 +02:00
summary.print()
if not result:
sys.exit(1)
@click.group()
2021-09-18 22:06:34 +02:00
def main(**kwargs):
pass
2021-09-18 22:06:34 +02:00
main.add_command(clean)
2021-10-15 06:41:22 +02:00
main.add_command(check)
2021-09-18 22:06:34 +02:00
main.add_command(compare)
2021-10-15 06:41:22 +02:00
main.add_command(init)
2021-09-18 22:06:34 +02:00
main.add_command(sort)
2021-10-15 06:41:22 +02:00
main.add_command(update)
if __name__ == '__main__':
main()