From a15d966d939d10f5638c0d4ff7913564d46792ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Schw=C3=B6rer?= Date: Wed, 15 Dec 2021 14:16:30 +0100 Subject: [PATCH] Move loader to own python file and added cmd flag to suppress its output --- zspotify/config.py | 4 ++- zspotify/loader.py | 72 +++++++++++++++++++++++++++++++++++++ zspotify/termoutput.py | 8 ++++- zspotify/track.py | 10 +++--- zspotify/utils.py | 81 ------------------------------------------ 5 files changed, 87 insertions(+), 88 deletions(-) create mode 100644 zspotify/loader.py diff --git a/zspotify/config.py b/zspotify/config.py index be8a734..429a85b 100644 --- a/zspotify/config.py +++ b/zspotify/config.py @@ -29,6 +29,7 @@ PRINT_API_ERRORS = 'PRINT_API_ERRORS' TEMP_DOWNLOAD_DIR = 'TEMP_DOWNLOAD_DIR' MD_ALLGENRES = 'MD_ALLGENRES' MD_GENREDELIMITER = 'MD_GENREDELIMITER' +PRINT_PROGRESS_INFO = 'PRINT_PROGRESS_INFO' CONFIG_VALUES = { ROOT_PATH: { 'default': '../ZSpotify Music/', 'type': str, 'arg': '--root-path' }, @@ -52,7 +53,8 @@ CONFIG_VALUES = { PRINT_DOWNLOAD_PROGRESS: { 'default': 'True', 'type': bool, 'arg': '--print-download-progress' }, PRINT_ERRORS: { 'default': 'True', 'type': bool, 'arg': '--print-errors' }, PRINT_DOWNLOADS: { 'default': 'False', 'type': bool, 'arg': '--print-downloads' }, - PRINT_API_ERRORS: { 'default': 'False', 'type': bool, 'arg': '--print-api-errors' }, + PRINT_API_ERRORS: { 'default': 'False', 'type': bool, 'arg': '--print-api-errors' }, + PRINT_PROGRESS_INFO: { 'default': 'True', 'type': bool, 'arg': '--print-progress-info' }, MD_ALLGENRES: { 'default': 'False', 'type': bool, 'arg': '--md-allgenres' }, MD_GENREDELIMITER: { 'default': ';', 'type': str, 'arg': '--md-genredelimiter' }, TEMP_DOWNLOAD_DIR: { 'default': '', 'type': str, 'arg': '--temp-download-dir' } diff --git a/zspotify/loader.py b/zspotify/loader.py new file mode 100644 index 0000000..faa3cb6 --- /dev/null +++ b/zspotify/loader.py @@ -0,0 +1,72 @@ +# load symbol from: +# https://stackoverflow.com/questions/22029562/python-how-to-make-simple-animated-loading-while-process-is-running + +# imports +from itertools import cycle +from shutil import get_terminal_size +from threading import Thread +from time import sleep + +from termoutput import Printer + + +class Loader: + """Busy symbol. + + Can be called inside a context: + + with Loader("This take some Time..."): + # do something + pass + """ + def __init__(self, chan, desc="Loading...", end='', timeout=0.1, mode='std1'): + """ + A loader-like context manager + + Args: + desc (str, optional): The loader's description. Defaults to "Loading...". + end (str, optional): Final print. Defaults to "". + timeout (float, optional): Sleep time between prints. Defaults to 0.1. + """ + self.desc = desc + self.end = end + self.timeout = timeout + self.channel = chan + + self._thread = Thread(target=self._animate, daemon=True) + if mode == 'std1': + self.steps = ["⢿", "⣻", "⣽", "⣾", "⣷", "⣯", "⣟", "⡿"] + elif mode == 'std2': + self.steps = ["◜","◝","◞","◟"] + elif mode == 'std3': + self.steps = ["😐 ","😐 ","😮 ","😮 ","😦 ","😦 ","😧 ","😧 ","🤯 ","💥 ","✨ ","\u3000 ","\u3000 ","\u3000 "] + elif mode == 'prog': + self.steps = ["[∙∙∙]","[●∙∙]","[∙●∙]","[∙∙●]","[∙∙∙]"] + + self.done = False + + def start(self): + self._thread.start() + return self + + def _animate(self): + for c in cycle(self.steps): + if self.done: + break + Printer.print_loader(self.channel, f"\r\t{c} {self.desc} ") + sleep(self.timeout) + + def __enter__(self): + self.start() + + def stop(self): + self.done = True + cols = get_terminal_size((80, 20)).columns + Printer.print_loader(self.channel, "\r" + " " * cols) + + if self.end != "": + Printer.print_loader(self.channel, f"\r{self.end}") + + def __exit__(self, exc_type, exc_value, tb): + # handle exceptions with those variables ^ + self.stop() diff --git a/zspotify/termoutput.py b/zspotify/termoutput.py index 06eec98..2615531 100644 --- a/zspotify/termoutput.py +++ b/zspotify/termoutput.py @@ -1,7 +1,7 @@ from enum import Enum from tqdm import tqdm -from config import PRINT_SPLASH, PRINT_SKIPS, PRINT_DOWNLOAD_PROGRESS, PRINT_ERRORS, PRINT_DOWNLOADS, PRINT_API_ERRORS +from config import * from zspotify import ZSpotify @@ -12,6 +12,7 @@ class PrintChannel(Enum): ERRORS = PRINT_ERRORS DOWNLOADS = PRINT_DOWNLOADS API_ERRORS = PRINT_API_ERRORS + PROGRESS_INFO = PRINT_PROGRESS_INFO class Printer: @@ -20,6 +21,11 @@ class Printer: if ZSpotify.CONFIG.get(channel.value): print(msg) + @staticmethod + def print_loader(channel: PrintChannel, msg: str) -> None: + if ZSpotify.CONFIG.get(channel.value): + print(msg, flush=True, end="") + @staticmethod def progress(iterable=None, desc=None, total=None, unit='it', disable=False, unit_scale=False, unit_divisor=1000): if not ZSpotify.CONFIG.get(PrintChannel.DOWNLOAD_PROGRESS.value): diff --git a/zspotify/track.py b/zspotify/track.py index 74704ea..7777709 100644 --- a/zspotify/track.py +++ b/zspotify/track.py @@ -17,7 +17,7 @@ from utils import fix_filename, set_audio_tags, set_music_thumbnail, create_down from zspotify import ZSpotify import traceback -from utils import Loader +from loader import Loader def get_saved_tracks() -> list: """ Returns user's saved tracks """ @@ -38,7 +38,7 @@ def get_saved_tracks() -> list: def get_song_info(song_id) -> Tuple[List[str], List[str], str, str, Any, Any, Any, Any, Any, Any, int]: """ Retrieves metadata for downloaded songs """ - with Loader("Fetching track information..."): + with Loader(PrintChannel.PROGRESS_INFO, "Fetching track information..."): (raw, info) = ZSpotify.invoke_url(f'{TRACKS_URL}?ids={song_id}&market=from_token') if not TRACKS in info: @@ -50,7 +50,7 @@ def get_song_info(song_id) -> Tuple[List[str], List[str], str, str, Any, Any, An for data in info[TRACKS][0][ARTISTS]: artists.append(data[NAME]) # query artist genres via href, which will be the api url - with Loader("Fetching artist information..."): + with Loader(PrintChannel.PROGRESS_INFO, "Fetching artist information..."): (raw, artistInfo) = ZSpotify.invoke_url(f'{data["href"]}') if ZSpotify.CONFIG.get_allGenres() and len(artistInfo[GENRES]) > 0: for genre in artistInfo[GENRES]: @@ -103,7 +103,7 @@ def download_track(mode: str, track_id: str, extra_keys={}, disable_progressbar= (artists, genres, album_name, name, image_url, release_year, disc_number, track_number, scraped_song_id, is_playable, duration_ms) = get_song_info(track_id) - prepareDownloadLoader = Loader("Preparing download..."); + prepareDownloadLoader = Loader(PrintChannel.PROGRESS_INFO, "Preparing download..."); prepareDownloadLoader.start() song_name = fix_filename(artists[0]) + ' - ' + fix_filename(name) @@ -251,7 +251,7 @@ def convert_audio_format(filename) -> None: outputs={filename: output_params} ) - with Loader("Converting file..."): + with Loader(PrintChannel.PROGRESS_INFO, "Converting file..."): ff_m.run() if os.path.exists(temp_filename): diff --git a/zspotify/utils.py b/zspotify/utils.py index ee20cec..f56ce5d 100644 --- a/zspotify/utils.py +++ b/zspotify/utils.py @@ -280,84 +280,3 @@ def fmt_seconds(secs: float) -> str: return f'{m}'.zfill(2) + ':' + f'{s}'.zfill(2) else: return f'{h}'.zfill(2) + ':' + f'{m}'.zfill(2) + ':' + f'{s}'.zfill(2) - - -# load symbol from: -# https://stackoverflow.com/questions/22029562/python-how-to-make-simple-animated-loading-while-process-is-running - -# imports -from itertools import cycle -from shutil import get_terminal_size -from threading import Thread -from time import sleep - -class Loader: - """Busy symbol. - - Can be called inside a context: - - with Loader("This take some Time..."): - # do something - pass - """ - def __init__(self, desc="Loading...", end='', timeout=0.1, mode='std1'): - """ - A loader-like context manager - - Args: - desc (str, optional): The loader's description. Defaults to "Loading...". - end (str, optional): Final print. Defaults to "". - timeout (float, optional): Sleep time between prints. Defaults to 0.1. - """ - self.desc = desc - self.end = end - self.timeout = timeout - - self._thread = Thread(target=self._animate, daemon=True) - if mode == 'std1': - self.steps = ["⢿", "⣻", "⣽", "⣾", "⣷", "⣯", "⣟", "⡿"] - elif mode == 'std2': - self.steps = ["◜","◝","◞","◟"] - elif mode == 'std3': - self.steps = ["😐 ","😐 ","😮 ","😮 ","😦 ","😦 ","😧 ","😧 ","🤯 ","💥 ","✨ ","\u3000 ","\u3000 ","\u3000 "] - elif mode == 'prog': - self.steps = ["[∙∙∙]","[●∙∙]","[∙●∙]","[∙∙●]","[∙∙∙]"] - - self.done = False - - def start(self): - self._thread.start() - return self - - def _animate(self): - for c in cycle(self.steps): - if self.done: - break - print(f"\r\t{c} {self.desc} ", flush=True, end="") - sleep(self.timeout) - - def __enter__(self): - self.start() - - def stop(self): - self.done = True - cols = get_terminal_size((80, 20)).columns - print("\r" + " " * cols, end="", flush=True) - - if self.end != "": - print(f"\r{self.end}", flush=True) - - def __exit__(self, exc_type, exc_value, tb): - # handle exceptions with those variables ^ - self.stop() - - -if __name__ == "__main__": - with Loader("Loading with context manager..."): - for i in range(10): - sleep(0.25) - - loader = Loader("Loading with object...", "That was fast!", 0.05).start() - for i in range(10): - sleep(0.25) - loader.stop()