diff --git a/audio-renamer.py b/audio-renamer.py index 548ddddea9094648c3aaba8682621aab09a16626..eda347e9c3dcf889c340d273864a149050f1c72b 100755 --- a/audio-renamer.py +++ b/audio-renamer.py @@ -3,6 +3,8 @@ import argparse import os from typing import Iterator, List +from customio.StatusPrinter import StatusPrinter +from customio.streams import stdout from files.FileFinder import FileFinder from files.FileMover import FileMover from models.FileMove import FileMove @@ -10,22 +12,30 @@ from models.FileMove import FileMove def process_folders(folders: List[str]) -> Iterator[FileMove]: for foldername in folders: + stdout().status("Reading folder… {}".format(foldername)) folder = os.path.abspath(foldername) finder = FileFinder(folder) mover = FileMover(folder) + status = StatusPrinter(stdout(), "Reading Files", finder.count()) for file in finder.all(): move = mover.move(file) + status.update(os.path.relpath(file, foldername)) if move is not None: yield move if __name__ == "__main__": + stdout().status("Initializing…") parser = argparse.ArgumentParser() parser.add_argument("folders", nargs="+", help="Folders to run the task on") parser.add_argument("--dry-run", help="Simulate renames", action="store_true") args = parser.parse_args() moves = list(process_folders(args.folders)) + statusTmp = StatusPrinter(stdout(), "Creating temporary files", len(moves)) for move in moves: + statusTmp.update(move.source) move.move_intermediate(args.dry_run) + statusFinal = StatusPrinter(stdout(), "Writing files", len(moves)) for move in moves: + statusTmp.update(move.source) move.move_target(args.dry_run) diff --git a/customio/OtherOutput.py b/customio/OtherOutput.py new file mode 100644 index 0000000000000000000000000000000000000000..755863f647401fc838917b8da5d45effae49ea0d --- /dev/null +++ b/customio/OtherOutput.py @@ -0,0 +1,16 @@ +import io + +from customio.Output import Output + + +class OtherOutput(Output): + file: io.TextIOBase + + def __init__(self, file: io.TextIOBase): + self.file = file + + def status(self, data: str): + pass + + def log(self, data: str): + print(data, file=self.file) diff --git a/customio/Output.py b/customio/Output.py new file mode 100644 index 0000000000000000000000000000000000000000..9a04064fa29d6a6d0b9d4eba0d43a3a66cf99406 --- /dev/null +++ b/customio/Output.py @@ -0,0 +1,17 @@ +# noinspection PyMethodMayBeStatic +class Output: + def status(self, data: str): + """ + Print a temporary line of data + :param data: content to print + :return: Nothing + """ + pass + + def log(self, data: str): + """ + Print a line of data + :param data: content to print + :return: Nothing + """ + pass diff --git a/customio/StatusPrinter.py b/customio/StatusPrinter.py new file mode 100644 index 0000000000000000000000000000000000000000..1444ca20c45ede6384f87807a9f8caa8d929a214 --- /dev/null +++ b/customio/StatusPrinter.py @@ -0,0 +1,22 @@ +from customio import Output + + +class StatusPrinter: + task: str + index: int + total: int + + def __init__(self, output: Output, task: str, total: int): + self.output = output + self.task = task + self.index = 0 + self.total = total + + def update(self, file: str): + data = self.task + '… ' + if self.total != 0: + data += "{} / {} ".format(self.index, self.total) + if file != '': + data += str(file) + self.output.status(data) + self.index += 1 diff --git a/customio/TerminalOutput.py b/customio/TerminalOutput.py new file mode 100644 index 0000000000000000000000000000000000000000..a73e428133cac8c1395b03acf5d9aee9c9b38361 --- /dev/null +++ b/customio/TerminalOutput.py @@ -0,0 +1,44 @@ +import io +import shutil + +from urwid import str_util + +from customio.Output import Output + + +class TerminalOutput(Output): + file: io.TextIOBase + + def __init__(self, file: io.TextIOBase): + self.file = file + + def status(self, text: str): + print("\x1B[K", end='') + print(TerminalOutput.__truncate_terminal(text), end='\r', flush=True, file=self.file) + + def log(self, data: str, error: bool = False): + if self.file.isatty(): + print("\x1B[K", end="", file=self.file) + if error: + print("\x1B[1;31m", end="", file=self.file) + print(data, file=self.file) + + @staticmethod + def __truncate_terminal(text: str) -> str: + columns = shutil.get_terminal_size((-1, -1)).columns + if columns <= 0: + return text + else: + return TerminalOutput.__truncate_width(text, columns) + + @staticmethod + def __truncate_width(text, length): + result = "" + width = 0 + for char in text: + charwidth = str_util.get_width(ord(char)) + if width + charwidth >= length: + break + result += char + width += charwidth + return result diff --git a/customio/streams.py b/customio/streams.py new file mode 100644 index 0000000000000000000000000000000000000000..3145196e5fb3e45d532a67a9858ad599d7e07034 --- /dev/null +++ b/customio/streams.py @@ -0,0 +1,23 @@ +import io +import sys + +from customio.Output import Output +from customio.OtherOutput import OtherOutput +from customio.TerminalOutput import TerminalOutput + + +def get_output(target: io.TextIOBase) -> Output: + if target.isatty(): + return TerminalOutput(target) + else: + return OtherOutput(target) + + +def stdout(): + # noinspection PyTypeChecker + return get_output(sys.stdout) + + +def stderr(): + # noinspection PyTypeChecker + return get_output(sys.stderr) diff --git a/files/FileFinder.py b/files/FileFinder.py index be6f988efda532d51d1080af386c04ce3a5696ba..fb6f41e3741454765dd758064c1512fd787995c6 100644 --- a/files/FileFinder.py +++ b/files/FileFinder.py @@ -12,3 +12,9 @@ class FileFinder: for subdir, dirs, files in os.walk(self.folder): for filename in files: yield os.path.join(self.folder, subdir, filename) + + def count(self) -> int: + count = 0 + for subdir, dirs, files in os.walk(self.folder): + count += len(files) + return count diff --git a/files/FileMover.py b/files/FileMover.py index 34a8c60f8fe6837e454ce64635cd0c1ae3786693..fb229a51f0448b4e8a229bc19e40dd4f97359a9f 100644 --- a/files/FileMover.py +++ b/files/FileMover.py @@ -2,6 +2,7 @@ import os from typing import Optional from uuid import uuid4 +from customio.streams import stderr from extractors.get_extractor import get_extractor from files.FileMetaParser import FileMetaParser from models.FileMeta import FileMeta @@ -38,15 +39,15 @@ class FileMover: def move(self, name: str) -> Optional[FileMove]: extractor = get_extractor(name) if extractor is None: - print("No Extractor for file: {0}".format(name)) + stderr().log("No Extractor for file: {0}".format(name)) return None track_meta = extractor.extract_tags() if track_meta is None: - print("No Metadata for file: {0}".format(name)) + stderr().log("No Metadata for file: {0}".format(name)) return None file_meta = FileMetaParser(track_meta).get_meta() if file_meta is None: - print("Metadata for file not valid: {0}".format(name)) + stderr().log("Metadata for file not valid: {0}".format(name)) return None target = self.__get_target_path(name, file_meta) diff --git a/models/FileMove.py b/models/FileMove.py index d1a6870ab68f2dfc2afa14be4b937af76ba46118..a2f6d2b8609b77fb2e721091213cd530510a4b62 100644 --- a/models/FileMove.py +++ b/models/FileMove.py @@ -1,5 +1,7 @@ import os +from customio.streams import stdout + class FileMove: source: str @@ -20,9 +22,9 @@ class FileMove: def move_target(self, dry_run: bool = True): if dry_run: - print("Moving File:") - print(" ↠{0}".format(self.source)) - print(" → {0}".format(self.target)) + stdout().log("Moving File:") + stdout().log(" ↠{0}".format(self.source)) + stdout().log(" → {0}".format(self.target)) else: target_folder = os.path.dirname(self.target) if not os.path.exists(target_folder):