From 1120592f0fd643c5e05e00c49aa718cc2e33f5a0 Mon Sep 17 00:00:00 2001 From: Leon Bohmann Date: Tue, 30 Nov 2021 14:45:06 +0100 Subject: [PATCH 01/12] added genre retrieval --- zspotify/config.py | 14 +++++++++++++- zspotify/const.py | 4 ++++ zspotify/track.py | 20 +++++++++++++++----- zspotify/utils.py | 5 +++-- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/zspotify/config.py b/zspotify/config.py index 0ba7d6d..0c4e9cf 100644 --- a/zspotify/config.py +++ b/zspotify/config.py @@ -26,6 +26,8 @@ PRINT_DOWNLOAD_PROGRESS = 'PRINT_DOWNLOAD_PROGRESS' PRINT_ERRORS = 'PRINT_ERRORS' PRINT_DOWNLOADS = 'PRINT_DOWNLOADS' TEMP_DOWNLOAD_DIR = 'TEMP_DOWNLOAD_DIR' +MD_ALLGENRES = 'MD_ALLGENRES' +MD_GENREDELIMITER = 'MD_GENREDELIMITER' CONFIG_VALUES = { ROOT_PATH: { 'default': '../ZSpotify Music/', 'type': str, 'arg': '--root-path' }, @@ -49,7 +51,9 @@ 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' }, - TEMP_DOWNLOAD_DIR: { 'default': '', 'type': str, 'arg': '--temp-download-dir' }, + 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' } } OUTPUT_DEFAULT_PLAYLIST = '{playlist}/{artist} - {song_name}.{ext}' @@ -192,7 +196,15 @@ class Config: if cls.get(TEMP_DOWNLOAD_DIR) == '': return '' return os.path.join(cls.get_root_path(), cls.get(TEMP_DOWNLOAD_DIR)) + + @classmethod + def get_allGenres(cls) -> bool: + return cls.get(MD_ALLGENRES) + @classmethod + def get_allGenresDelimiter(cls) -> bool: + return cls.get(MD_GENREDELIMITER) + @classmethod def get_output(cls, mode: str) -> str: v = cls.get(OUTPUT) diff --git a/zspotify/const.py b/zspotify/const.py index 057921c..3e76f8a 100644 --- a/zspotify/const.py +++ b/zspotify/const.py @@ -20,6 +20,10 @@ ARTISTS = 'artists' ALBUMARTIST = 'albumartist' +GENRES = 'genres' + +GENRE = 'genre' + ARTWORK = 'artwork' TRACKS = 'tracks' diff --git a/zspotify/track.py b/zspotify/track.py index 1278412..faa630b 100644 --- a/zspotify/track.py +++ b/zspotify/track.py @@ -8,7 +8,7 @@ from librespot.audio.decoders import AudioQuality from librespot.metadata import TrackId from ffmpy import FFmpeg -from const import TRACKS, ALBUM, NAME, ITEMS, DISC_NUMBER, TRACK_NUMBER, IS_PLAYABLE, ARTISTS, IMAGES, URL, \ +from const import TRACKS, ALBUM, GENRES, GENRE, NAME, ITEMS, DISC_NUMBER, TRACK_NUMBER, IS_PLAYABLE, ARTISTS, IMAGES, URL, \ RELEASE_DATE, ID, TRACKS_URL, SAVED_TRACKS_URL, TRACK_STATS_URL, CODEC_MAP, EXT_MAP, DURATION_MS from termoutput import Printer, PrintChannel from utils import fix_filename, set_audio_tags, set_music_thumbnail, create_download_directory, \ @@ -33,7 +33,7 @@ def get_saved_tracks() -> list: return songs -def get_song_info(song_id) -> Tuple[List[str], str, str, Any, Any, Any, Any, Any, Any, int]: +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 """ (raw, info) = ZSpotify.invoke_url(f'{TRACKS_URL}?ids={song_id}&market=from_token') @@ -42,8 +42,18 @@ def get_song_info(song_id) -> Tuple[List[str], str, str, Any, Any, Any, Any, Any try: artists = [] + genres = [] for data in info[TRACKS][0][ARTISTS]: artists.append(data[NAME]) + # query artist genres via href, which will be the api url + (raw, artistInfo) = ZSpotify.invoke_url(f'{data["href"]}') + if ZSpotify.CONFIG.get_allGenres() and len(artistInfo[GENRES]) > 0: + for genre in artistInfo[GENRES]: + genres.append(genre) + elif len(artistInfo[GENRES]) > 0: + genres.append(artistInfo[GENRES][0]) + else: + genres.append('') album_name = info[TRACKS][0][ALBUM][NAME] name = info[TRACKS][0][NAME] image_url = info[TRACKS][0][ALBUM][IMAGES][0][URL] @@ -54,7 +64,7 @@ def get_song_info(song_id) -> Tuple[List[str], str, str, Any, Any, Any, Any, Any is_playable = info[TRACKS][0][IS_PLAYABLE] duration_ms = info[TRACKS][0][DURATION_MS] - return artists, album_name, name, image_url, release_year, disc_number, track_number, scraped_song_id, is_playable, duration_ms + return artists, genres, album_name, name, image_url, release_year, disc_number, track_number, scraped_song_id, is_playable, duration_ms except Exception as e: raise ValueError(f'Failed to parse TRACKS_URL response: {str(e)}\n{raw}') @@ -82,7 +92,7 @@ def download_track(mode: str, track_id: str, extra_keys={}, disable_progressbar= try: output_template = ZSpotify.CONFIG.get_output(mode) - (artists, album_name, name, image_url, release_year, disc_number, + (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) song_name = fix_filename(artists[0]) + ' - ' + fix_filename(name) @@ -170,7 +180,7 @@ def download_track(mode: str, track_id: str, extra_keys={}, disable_progressbar= time_downloaded = time.time() convert_audio_format(filename_temp) - set_audio_tags(filename_temp, artists, name, album_name, release_year, disc_number, track_number) + set_audio_tags(filename_temp, artists, genres, name, album_name, release_year, disc_number, track_number) set_music_thumbnail(filename_temp, image_url) if filename_temp != filename: diff --git a/zspotify/utils.py b/zspotify/utils.py index 3656145..f56ce5d 100644 --- a/zspotify/utils.py +++ b/zspotify/utils.py @@ -10,7 +10,7 @@ from typing import List, Tuple import music_tag import requests -from const import ARTIST, TRACKTITLE, ALBUM, YEAR, DISCNUMBER, TRACKNUMBER, ARTWORK, \ +from const import ARTIST, GENRE, TRACKTITLE, ALBUM, YEAR, DISCNUMBER, TRACKNUMBER, ARTWORK, \ WINDOWS_SYSTEM, ALBUMARTIST from zspotify import ZSpotify @@ -124,11 +124,12 @@ def clear() -> None: os.system('clear') -def set_audio_tags(filename, artists, name, album_name, release_year, disc_number, track_number) -> None: +def set_audio_tags(filename, artists, genres, name, album_name, release_year, disc_number, track_number) -> None: """ sets music_tag metadata """ tags = music_tag.load_file(filename) tags[ALBUMARTIST] = artists[0] tags[ARTIST] = conv_artist_format(artists) + tags[GENRE] = genres[0] if not ZSpotify.CONFIG.get_allGenres() else ZSpotify.CONFIG.get_allGenresDelimiter().join(genres) tags[TRACKTITLE] = name tags[ALBUM] = album_name tags[YEAR] = release_year From 0d553243d2b4ec5a8c52f9ae591d9ca0d834ebed Mon Sep 17 00:00:00 2001 From: Leon Bohmann Date: Tue, 30 Nov 2021 17:01:34 +0100 Subject: [PATCH 02/12] added switch to not send too much requests --- zspotify/track.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/zspotify/track.py b/zspotify/track.py index faa630b..b58abbe 100644 --- a/zspotify/track.py +++ b/zspotify/track.py @@ -43,17 +43,23 @@ def get_song_info(song_id) -> Tuple[List[str], List[str], str, str, Any, Any, An try: artists = [] genres = [] + genreRetrieved = False for data in info[TRACKS][0][ARTISTS]: artists.append(data[NAME]) - # query artist genres via href, which will be the api url - (raw, artistInfo) = ZSpotify.invoke_url(f'{data["href"]}') - if ZSpotify.CONFIG.get_allGenres() and len(artistInfo[GENRES]) > 0: - for genre in artistInfo[GENRES]: - genres.append(genre) - elif len(artistInfo[GENRES]) > 0: - genres.append(artistInfo[GENRES][0]) - else: - genres.append('') + + if not genreRetrieved: + # query artist genres via href, which will be the api url + (raw, artistInfo) = ZSpotify.invoke_url(f'{data["href"]}') + if ZSpotify.CONFIG.get_allGenres() and len(artistInfo[GENRES]) > 0: + genreRetrieved = False + for genre in artistInfo[GENRES]: + genres.append(genre) + elif len(artistInfo[GENRES]) > 0: + genres.append(artistInfo[GENRES][0]) + genreRetrieved = True + else: + genres.append('') + genreRetrieved = True album_name = info[TRACKS][0][ALBUM][NAME] name = info[TRACKS][0][NAME] image_url = info[TRACKS][0][ALBUM][IMAGES][0][URL] From 702ec22094e6707c508073e6788491f5e7fb020c Mon Sep 17 00:00:00 2001 From: Leon Bohmann Date: Tue, 30 Nov 2021 20:20:06 +0100 Subject: [PATCH 03/12] updated exception handling --- zspotify/track.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/zspotify/track.py b/zspotify/track.py index b58abbe..1deed40 100644 --- a/zspotify/track.py +++ b/zspotify/track.py @@ -1,5 +1,6 @@ import os import re +from threading import Thread import time import uuid from typing import Any, Tuple, List @@ -46,20 +47,24 @@ def get_song_info(song_id) -> Tuple[List[str], List[str], str, str, Any, Any, An genreRetrieved = False for data in info[TRACKS][0][ARTISTS]: artists.append(data[NAME]) - - if not genreRetrieved: - # query artist genres via href, which will be the api url - (raw, artistInfo) = ZSpotify.invoke_url(f'{data["href"]}') - if ZSpotify.CONFIG.get_allGenres() and len(artistInfo[GENRES]) > 0: - genreRetrieved = False - for genre in artistInfo[GENRES]: - genres.append(genre) - elif len(artistInfo[GENRES]) > 0: - genres.append(artistInfo[GENRES][0]) - genreRetrieved = True - else: + try: + if not genreRetrieved: + # query artist genres via href, which will be the api url + (raw, artistInfo) = ZSpotify.invoke_url(f'{data["href"]}') + if ZSpotify.CONFIG.get_allGenres() and len(artistInfo[GENRES]) > 0: + genreRetrieved = False + for genre in artistInfo[GENRES]: + genres.append(genre) + elif len(artistInfo[GENRES]) > 0: + genres.append(artistInfo[GENRES][0]) + genreRetrieved = True + else: + genres.append('') + genreRetrieved = True + except Exception as genreError: + if len(genres) == 0: genres.append('') - genreRetrieved = True + album_name = info[TRACKS][0][ALBUM][NAME] name = info[TRACKS][0][NAME] image_url = info[TRACKS][0][ALBUM][IMAGES][0][URL] From 16c7ee5d51531b90612254a86dfd102dada1350b Mon Sep 17 00:00:00 2001 From: Leon Bohmann Date: Tue, 30 Nov 2021 21:41:33 +0100 Subject: [PATCH 04/12] removed exception handling from track --- zspotify/track.py | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/zspotify/track.py b/zspotify/track.py index 1deed40..6ebbf28 100644 --- a/zspotify/track.py +++ b/zspotify/track.py @@ -44,27 +44,20 @@ def get_song_info(song_id) -> Tuple[List[str], List[str], str, str, Any, Any, An try: artists = [] genres = [] - genreRetrieved = False for data in info[TRACKS][0][ARTISTS]: artists.append(data[NAME]) - try: - if not genreRetrieved: - # query artist genres via href, which will be the api url - (raw, artistInfo) = ZSpotify.invoke_url(f'{data["href"]}') - if ZSpotify.CONFIG.get_allGenres() and len(artistInfo[GENRES]) > 0: - genreRetrieved = False - for genre in artistInfo[GENRES]: - genres.append(genre) - elif len(artistInfo[GENRES]) > 0: - genres.append(artistInfo[GENRES][0]) - genreRetrieved = True - else: - genres.append('') - genreRetrieved = True - except Exception as genreError: - if len(genres) == 0: - genres.append('') - + # query artist genres via href, which will be the api url + (raw, artistInfo) = ZSpotify.invoke_url(f'{data["href"]}') + if ZSpotify.CONFIG.get_allGenres() and len(artistInfo[GENRES]) > 0: + for genre in artistInfo[GENRES]: + genres.append(genre) + elif len(artistInfo[GENRES]) > 0: + genres.append(artistInfo[GENRES][0]) + + if len(genres) == 0: + Printer.print(PrintChannel.SKIPS, "No Genre found.") + genres.append('') + album_name = info[TRACKS][0][ALBUM][NAME] name = info[TRACKS][0][NAME] image_url = info[TRACKS][0][ALBUM][IMAGES][0][URL] From 9a48746b790ea122a383cf7c47a64d2292741caa Mon Sep 17 00:00:00 2001 From: Leon Bohmann Date: Tue, 30 Nov 2021 21:41:48 +0100 Subject: [PATCH 05/12] added exception handling and retry-logic to zspotify --- zspotify/zspotify.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/zspotify/zspotify.py b/zspotify/zspotify.py index f398d5d..868e9e2 100644 --- a/zspotify/zspotify.py +++ b/zspotify/zspotify.py @@ -9,7 +9,7 @@ It's like youtube-dl, but for Spotify. import os import os.path from getpass import getpass - +import time import requests from librespot.audio.decoders import VorbisOnlyAudioQuality from librespot.core import Session @@ -19,7 +19,6 @@ from const import TYPE, \ PLAYLIST_READ_PRIVATE, USER_LIBRARY_READ from config import Config - class ZSpotify: SESSION: Session = None DOWNLOAD_QUALITY = None @@ -82,10 +81,19 @@ class ZSpotify: return requests.get(url, headers=headers, params=params).json() @classmethod - def invoke_url(cls, url): + def invoke_url(cls, url, tryCount = 0): headers = cls.get_auth_header() response = requests.get(url, headers=headers) - return response.text, response.json() + responseText = response.text + responseJson = response.json() + + if 'error' in responseJson and tryCount < 20: + + print(f"Spotify API Error ({responseJson['error']['status']}): {responseJson['error']['message']}") + time.sleep(5) + return cls.invoke_url(url, tryCount + 1) + + return responseText, responseJson @classmethod def check_premium(cls) -> bool: From 401688a4581c995848f42589877abb6bae6ce7bd Mon Sep 17 00:00:00 2001 From: Leon Bohmann Date: Tue, 30 Nov 2021 22:11:59 +0100 Subject: [PATCH 06/12] added loader class for long running tasks --- zspotify/utils.py | 81 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/zspotify/utils.py b/zspotify/utils.py index f56ce5d..20d6d45 100644 --- a/zspotify/utils.py +++ b/zspotify/utils.py @@ -280,3 +280,84 @@ 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.3, mode='std2'): + """ + 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() From a93fc0ee36234f9745303fd8ab2b71d86122f180 Mon Sep 17 00:00:00 2001 From: Leon Bohmann Date: Tue, 30 Nov 2021 22:12:47 +0100 Subject: [PATCH 07/12] implemented loaders in task - long running tasks like zspotify.invoke_url or convert_audio_format now use a Loader to show that they are busy --- zspotify/track.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/zspotify/track.py b/zspotify/track.py index 6ebbf28..47c0e6a 100644 --- a/zspotify/track.py +++ b/zspotify/track.py @@ -17,6 +17,8 @@ from utils import fix_filename, set_audio_tags, set_music_thumbnail, create_down from zspotify import ZSpotify import traceback +from utils import Loader + def get_saved_tracks() -> list: """ Returns user's saved tracks """ songs = [] @@ -36,7 +38,8 @@ 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 """ - (raw, info) = ZSpotify.invoke_url(f'{TRACKS_URL}?ids={song_id}&market=from_token') + with Loader("Fetching track information..."): + (raw, info) = ZSpotify.invoke_url(f'{TRACKS_URL}?ids={song_id}&market=from_token') if not TRACKS in info: raise ValueError(f'Invalid response from TRACKS_URL:\n{raw}') @@ -47,7 +50,8 @@ 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 - (raw, artistInfo) = ZSpotify.invoke_url(f'{data["href"]}') + with Loader("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]: genres.append(genre) @@ -238,6 +242,9 @@ def convert_audio_format(filename) -> None: inputs={temp_filename: None}, outputs={filename: output_params} ) - ff_m.run() + + with Loader("Converting file..."): + ff_m.run() + if os.path.exists(temp_filename): os.remove(temp_filename) From 6877cca21b4983142c04564888e759b096e0bded Mon Sep 17 00:00:00 2001 From: Leon Bohmann Date: Tue, 30 Nov 2021 22:15:55 +0100 Subject: [PATCH 08/12] small design update to loader --- zspotify/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zspotify/utils.py b/zspotify/utils.py index 20d6d45..bc1f279 100644 --- a/zspotify/utils.py +++ b/zspotify/utils.py @@ -333,7 +333,7 @@ class Loader: for c in cycle(self.steps): if self.done: break - print(f"\r\t☐ {c} {self.desc} ", flush=True, end="") + print(f"\r\t{c} {self.desc} ", flush=True, end="") sleep(self.timeout) def __enter__(self): From 055f7ad97d5bce294638e2a753f4623e407c892f Mon Sep 17 00:00:00 2001 From: Leon Bohmann Date: Tue, 30 Nov 2021 22:33:50 +0100 Subject: [PATCH 09/12] added another loader for download preparation --- zspotify/track.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/zspotify/track.py b/zspotify/track.py index 47c0e6a..3fec49a 100644 --- a/zspotify/track.py +++ b/zspotify/track.py @@ -102,7 +102,10 @@ 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.start() + song_name = fix_filename(artists[0]) + ' - ' + fix_filename(name) for k in extra_keys: @@ -149,12 +152,15 @@ def download_track(mode: str, track_id: str, extra_keys={}, disable_progressbar= else: try: if not is_playable: + prepareDownloadLoader.stop(); Printer.print(PrintChannel.SKIPS, '\n### SKIPPING: ' + song_name + ' (SONG IS UNAVAILABLE) ###' + "\n") else: if check_id and check_name and ZSpotify.CONFIG.get_skip_existing_files(): + prepareDownloadLoader.stop(); Printer.print(PrintChannel.SKIPS, '\n### SKIPPING: ' + song_name + ' (SONG ALREADY EXISTS) ###' + "\n") elif check_all_time and ZSpotify.CONFIG.get_skip_previously_downloaded(): + prepareDownloadLoader.stop(); Printer.print(PrintChannel.SKIPS, '\n### SKIPPING: ' + song_name + ' (SONG ALREADY DOWNLOADED ONCE) ###' + "\n") else: @@ -165,6 +171,8 @@ def download_track(mode: str, track_id: str, extra_keys={}, disable_progressbar= create_download_directory(filedir) total_size = stream.input_stream.size + prepareDownloadLoader.stop(); + time_start = time.time() downloaded = 0 with open(filename_temp, 'wb') as file, Printer.progress( @@ -214,8 +222,8 @@ def download_track(mode: str, track_id: str, extra_keys={}, disable_progressbar= Printer.print(PrintChannel.ERRORS, "".join(traceback.TracebackException.from_exception(e).format()) + "\n") if os.path.exists(filename_temp): os.remove(filename_temp) - - + + prepareDownloadLoader.stop() def convert_audio_format(filename) -> None: """ Converts raw audio into playable file """ temp_filename = f'{os.path.splitext(filename)[0]}.tmp' From c9e8c0c45debae2084a27fb377c8539ec0c50698 Mon Sep 17 00:00:00 2001 From: Leon Bohmann Date: Wed, 1 Dec 2021 13:48:53 +0100 Subject: [PATCH 10/12] use Printer class for output - added print-options for API_ERRORS - modified printing inside of invoke_url - "No Genre found." is printed using SKIPS --- zspotify/config.py | 2 ++ zspotify/termoutput.py | 3 ++- zspotify/track.py | 2 +- zspotify/zspotify.py | 6 ++++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/zspotify/config.py b/zspotify/config.py index 0c4e9cf..be8a734 100644 --- a/zspotify/config.py +++ b/zspotify/config.py @@ -25,6 +25,7 @@ PRINT_SKIPS = 'PRINT_SKIPS' PRINT_DOWNLOAD_PROGRESS = 'PRINT_DOWNLOAD_PROGRESS' PRINT_ERRORS = 'PRINT_ERRORS' PRINT_DOWNLOADS = 'PRINT_DOWNLOADS' +PRINT_API_ERRORS = 'PRINT_API_ERRORS' TEMP_DOWNLOAD_DIR = 'TEMP_DOWNLOAD_DIR' MD_ALLGENRES = 'MD_ALLGENRES' MD_GENREDELIMITER = 'MD_GENREDELIMITER' @@ -51,6 +52,7 @@ 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' }, 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/termoutput.py b/zspotify/termoutput.py index bf57e35..06eec98 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 +from config import PRINT_SPLASH, PRINT_SKIPS, PRINT_DOWNLOAD_PROGRESS, PRINT_ERRORS, PRINT_DOWNLOADS, PRINT_API_ERRORS from zspotify import ZSpotify @@ -11,6 +11,7 @@ class PrintChannel(Enum): DOWNLOAD_PROGRESS = PRINT_DOWNLOAD_PROGRESS ERRORS = PRINT_ERRORS DOWNLOADS = PRINT_DOWNLOADS + API_ERRORS = PRINT_API_ERRORS class Printer: diff --git a/zspotify/track.py b/zspotify/track.py index 3fec49a..74704ea 100644 --- a/zspotify/track.py +++ b/zspotify/track.py @@ -59,7 +59,7 @@ def get_song_info(song_id) -> Tuple[List[str], List[str], str, str, Any, Any, An genres.append(artistInfo[GENRES][0]) if len(genres) == 0: - Printer.print(PrintChannel.SKIPS, "No Genre found.") + Printer.print(PrintChannel.SKIPS, '### No Genre found.') genres.append('') album_name = info[TRACKS][0][ALBUM][NAME] diff --git a/zspotify/zspotify.py b/zspotify/zspotify.py index 868e9e2..1738507 100644 --- a/zspotify/zspotify.py +++ b/zspotify/zspotify.py @@ -19,7 +19,7 @@ from const import TYPE, \ PLAYLIST_READ_PRIVATE, USER_LIBRARY_READ from config import Config -class ZSpotify: +class ZSpotify: SESSION: Session = None DOWNLOAD_QUALITY = None CONFIG: Config = Config() @@ -82,6 +82,8 @@ class ZSpotify: @classmethod def invoke_url(cls, url, tryCount = 0): + # we need to import that here, otherwise we will get circular imports! + from termoutput import Printer, PrintChannel headers = cls.get_auth_header() response = requests.get(url, headers=headers) responseText = response.text @@ -89,7 +91,7 @@ class ZSpotify: if 'error' in responseJson and tryCount < 20: - print(f"Spotify API Error ({responseJson['error']['status']}): {responseJson['error']['message']}") + Printer.Print(PrintChannel.API_ERROR, f"Spotify API Error ({responseJson['error']['status']}): {responseJson['error']['message']}") time.sleep(5) return cls.invoke_url(url, tryCount + 1) From e00ad81c5ef999eb2b40ea6a6f7ed4369b92a7fc Mon Sep 17 00:00:00 2001 From: Leon Bohmann Date: Wed, 1 Dec 2021 13:49:24 +0100 Subject: [PATCH 11/12] use more fancy busySymbols --- zspotify/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zspotify/utils.py b/zspotify/utils.py index bc1f279..ee20cec 100644 --- a/zspotify/utils.py +++ b/zspotify/utils.py @@ -300,7 +300,7 @@ class Loader: # do something pass """ - def __init__(self, desc="Loading...", end='', timeout=0.3, mode='std2'): + def __init__(self, desc="Loading...", end='', timeout=0.1, mode='std1'): """ A loader-like context manager From 58fe9ae9e1962cf76335882cccdb7de273249ec9 Mon Sep 17 00:00:00 2001 From: Logykk <35679186+logykk@users.noreply.github.com> Date: Thu, 2 Dec 2021 17:20:32 +1300 Subject: [PATCH 12/12] Limit invoke_url retry's to 5 --- zspotify/zspotify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zspotify/zspotify.py b/zspotify/zspotify.py index 1738507..e535a9f 100644 --- a/zspotify/zspotify.py +++ b/zspotify/zspotify.py @@ -89,7 +89,7 @@ class ZSpotify: responseText = response.text responseJson = response.json() - if 'error' in responseJson and tryCount < 20: + if 'error' in responseJson and tryCount < 5: Printer.Print(PrintChannel.API_ERROR, f"Spotify API Error ({responseJson['error']['status']}): {responseJson['error']['message']}") time.sleep(5)