From 1e8b7d36a6175d6b59fe868eb5aede6bd30ae9ac Mon Sep 17 00:00:00 2001 From: Leonard Kugis Date: Tue, 21 Mar 2023 18:38:13 +0100 Subject: CLI, GUI Implemented better CLI compatibility. Implemented generic GUI for all file types. --- file-tagger.py | 78 +++++++++++++++++++++++++++++++++++++++++----------------- gui.py | 45 +++++++++++++++++++++++---------- 2 files changed, 88 insertions(+), 35 deletions(-) diff --git a/file-tagger.py b/file-tagger.py index 71a64e3..2e4fe40 100644 --- a/file-tagger.py +++ b/file-tagger.py @@ -1,12 +1,23 @@ import numpy as np import argparse import os, sys -from gui import GuiMain, GuiImage +from gui import GuiMain, GuiImage, GuiTag import cv2 import logging import magic -from subprocess import Popen, PIPE +import subprocess import re +import platform +import readline + +def input_with_prefill(prompt, text): + def hook(): + readline.insert_text(text) + readline.redisplay() + readline.set_pre_input_hook(hook) + result = input(prompt) + readline.set_pre_input_hook() + return result def dir_path(string): if os.path.isdir(string): @@ -14,6 +25,14 @@ def dir_path(string): else: raise NotADirectoryError(string) +def open_system(file): + if platform.system() == 'Darwin': # macOS + subprocess.call(('open', file)) + elif platform.system() == 'Windows': # Windows + os.startfile(file) + else: # linux variants + subprocess.call(('xdg-open', file)) + def tmsu_init(base): logger = logging.getLogger(__name__) if not os.path.exists(os.path.join(base, ".tmsu")): @@ -30,7 +49,7 @@ def tmsu_tags(base, file): logger = logging.getLogger(__name__) logger.debug("Getting existing tags for file {}".format(file)) tags = set() - proc = Popen(["tmsu", "tags", file], cwd=base, stdout=PIPE, stderr=PIPE) + proc = subprocess.Popen(["tmsu", "tags", file], cwd=base, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc.wait() logger.debug("TMSU returncode: {}".format(proc.returncode)) if proc.returncode == 0: @@ -43,13 +62,13 @@ def tmsu_tag(base, file, tags, untag=True): logger = logging.getLogger(__name__) if untag: logger.debug("Untagging file") - proc = Popen(["tmsu", "untag", "--all", file], cwd=base, stdout=PIPE, stderr=PIPE) + proc = subprocess.Popen(["tmsu", "untag", "--all", file], cwd=base, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc.wait() if proc.returncode != 0: logger.error("Could not untag file {}".format(file)) if tags: logger.debug("Writing tags {}".format(tags)) - proc = Popen(["tmsu", "tag", file] + list(tags), cwd=base, stdout=PIPE, stderr=PIPE) + proc = subprocess.Popen(["tmsu", "tag", file] + list(tags), cwd=base, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc.wait() if proc.returncode != 0: logger.error("Could not write tags to file {}".format(file)) @@ -75,24 +94,31 @@ def walk(args): tags = tmsu_tags(args["base"], file_path) not_empty = bool(tags) logger.info("Existing tags: {}".format(tags)) + + if args["open_system"]: + open_system(file_path) + mime_type = mime.from_file(file_path) + if mime_type.split("/")[0] == "image": logger.debug("File is image") img = cv2.imread(file_path) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = cv2.resize(img, dsize=(800, 800), interpolation=cv2.INTER_CUBIC) - while(True): - if args["predict_images"]: - logger.info("Predicting image tags ...") - array = cv2.resize(img, dsize=(224, 224), interpolation=cv2.INTER_CUBIC) - array = np.expand_dims(array, axis=0) + if args["predict_images"]: + logger.info("Predicting image tags ...") + array_pre = cv2.resize(img, dsize=(224, 224), interpolation=cv2.INTER_CUBIC) + for _ in range(4): + array = np.expand_dims(array_pre, axis=0) array = preprocess_input(array) predictions = model.predict(array) classes = decode_predictions(predictions, top=10) logger.debug("Predicted image classes: {}".format(classes[0])) tags.update([name for _, name, _ in classes[0]]) - logger.info("Predicted tags: {}".format(tags)) - if args["gui_images"]: + array_pre = cv2.rotate(array_pre, cv2.ROTATE_90_CLOCKWISE) + logger.info("Predicted tags: {}".format(tags)) + if args["gui_tag"]: + while(True): # For GUI inputs (rotate, ...) logger.debug("Showing image GUI ...") ret = GuiImage(img, tags).loop() tags = set(ret[1]).difference({''}) @@ -104,8 +130,20 @@ def walk(args): break elif ret[0] == GuiImage.RETURN_ABORT: return - continue - break + else: + if args["gui_tag"]: + while(True): + logger.debug("Showing generic tagging GUI ...") + ret = GuiTag(file_path, tags).loop() + tags = set(ret[1]).difference({''}) + if ret[0] == GuiTag.RETURN_NEXT: + break + elif ret[0] == GuiTag.RETURN_ABORT: + return + + if not args["gui_tag"]: + tags = set(input_with_prefill("\nTags for file {}:\n".format(file_path), ','.join(tags)).split(",")) + logger.info("Tagging {}".format(tags)) tmsu_tag(args["base"], file_path, tags, untag=not_empty) @@ -115,10 +153,8 @@ if __name__ == "__main__": parser.add_argument('-b', '--base', nargs='?', default='./test', type=dir_path, help='Base directory for walking (default: %(default)s)') parser.add_argument('-g', '--gui', nargs='?', const=1, default=False, type=bool, help='Show main GUI (default: %(default)s)') parser.add_argument('--predict-images', nargs='?', const=1, default=False, type=bool, help='Use prediction for image tagging (default: %(default)s)') - parser.add_argument('--gui-images', nargs='?', const=1, default=False, type=bool, help='Show GUI for image tagging (default: %(default)s)') - parser.add_argument('--gui-audio', nargs='?', const=1, default=False, type=bool, help='Show GUI for audio tagging (default: %(default)s)') - parser.add_argument('--gui-video', nargs='?', const=1, default=False, type=bool, help='Show GUI for video tagging (default: %(default)s)') - parser.add_argument('--open-all', nargs='?', const=1, default=False, type=bool, help='Open all files with system default (default: %(default)s)') + parser.add_argument('--gui-tag', nargs='?', const=1, default=False, type=bool, help='Show GUI for tagging (default: %(default)s)') + parser.add_argument('--open-system', nargs='?', const=1, default=False, type=bool, help='Open all files with system default (default: %(default)s)') parser.add_argument('-v', '--verbose', action="count", default=0, help="Verbosity level") args = parser.parse_args() @@ -136,10 +172,8 @@ if __name__ == "__main__": "base": args.base, "gui": args.gui, "predict_images": args.predict_images, - "gui_images": args.gui_images, - "gui_audio": args.gui_audio, - "gui_video": args.gui_video, - "open_all": args.open_all, + "gui_tag": args.gui_tag, + "open_system": args.open_system, "verbosity": args.verbose } diff --git a/gui.py b/gui.py index cb30534..227275f 100644 --- a/gui.py +++ b/gui.py @@ -11,36 +11,55 @@ class GuiMain(object): self.__args = args self.__base = StringVar(self.__master, value=args["base"]) self.__predict_images = BooleanVar(self.__master, value=args["predict_images"]) - self.__gui_images = BooleanVar(self.__master, value=args["gui_images"]) - self.__gui_audio = BooleanVar(self.__master, value=args["gui_audio"]) - self.__gui_video = BooleanVar(self.__master, value=args["gui_video"]) - self.__open_all = BooleanVar(self.__master, value=args["open_all"]) + self.__gui_tag = BooleanVar(self.__master, value=args["gui_tag"]) + self.__open_system = BooleanVar(self.__master, value=args["open_system"]) Label(self.__master, text="Base directory for walking:").grid(row=0, column=0) Entry(self.__master, textvariable=self.__base).grid(row=0, column=1) Button(self.__master, text="Browse", command=lambda: self.__browse(base)).grid(row=0, column=3) Checkbutton(self.__master, text="Use prediction for image tagging", variable=self.__predict_images).grid(row=1, sticky=W) - Checkbutton(self.__master, text="Show GUI for image tagging", variable=self.__gui_images).grid(row=2, sticky=W) - Checkbutton(self.__master, text="Show GUI for audio tagging", variable=self.__gui_audio).grid(row=3, sticky=W) - Checkbutton(self.__master, text="Show GUI for video tagging", variable=self.__gui_video).grid(row=4, sticky=W) - Checkbutton(self.__master, text="Open all files with system default", variable=self.__open_all).grid(row=5, sticky=W) - Button(self.__master, text="Start", command=self.__master.destroy).grid(row=6) + Checkbutton(self.__master, text="Show GUI for tagging", variable=self.__gui_tag).grid(row=2, sticky=W) + Checkbutton(self.__master, text="Open all files with system default", variable=self.__open_system).grid(row=3, sticky=W) + Button(self.__master, text="Start", command=self.__master.destroy).grid(row=4) def loop(self): self.__master.mainloop() self.__args["base"] = self.__base.get() self.__args["predict_images"] = self.__predict_images.get() - self.__args["gui_images"] = self.__gui_images.get() - self.__args["gui_audio"] = self.__gui_audio.get() - self.__args["gui_video"] = self.__gui_video.get() - self.__args["open_all"] = self.__open_all.get() + self.__args["gui_tag"] = self.__gui_tag.get() + self.__args["open_system"] = self.__open_system.get() return self.__args def __browse(self, folder_path): filename = filedialog.askdirectory() folder_path.set(filename) +class GuiTag(object): + RETURN_NEXT = 0 + RETURN_ABORT = 1 + + def __init__(self, file, tags): + self.__ret = self.RETURN_NEXT + self.__master = Tk() + self.__tags = StringVar(self.__master, value=','.join(tags)) + Label(self.__master, text="File: {}".format(file)).grid(row=0, column=0, columnspan=2) + Entry(self.__master, textvariable=self.__tags).grid(row=1, column=0, columnspan=2, sticky="we") + Button(self.__master, text="Next", command=self.__handle_next).grid(row=2, column=0) + Button(self.__master, text="Abort", command=self.__handle_abort).grid(row=2, column=1) + + def loop(self): + self.__master.mainloop() + return (self.__ret, self.__tags.get().split(",")) + + def __handle_next(self): + self.__ret = self.RETURN_NEXT + self.__master.destroy() + + def __handle_abort(self): + self.__ret = self.RETURN_ABORT + self.__master.destroy() + class GuiImage(object): RETURN_NEXT = 0, RETURN_ROTATE_90_COUNTERCLOCKWISE = 1, -- cgit v1.2.1