From f10ce45f05f7fb907d7521ed1ba0cf8e2135dd88 Mon Sep 17 00:00:00 2001 From: Jonathan Barratt Date: Sat, 23 Oct 2021 13:28:23 +0700 Subject: [PATCH] Moves hard-coded configuration to separate json file adds zs_config.json, changes global usage in zspotify, and updates README --- README.md | 19 ++++++++------- zs_config.json | 11 +++++++++ zspotify.py | 65 +++++++++++++++++--------------------------------- 3 files changed, 44 insertions(+), 51 deletions(-) create mode 100644 zs_config.json diff --git a/README.md b/README.md index de8c6b4..1074719 100644 --- a/README.md +++ b/README.md @@ -36,19 +36,19 @@ Extra command line options: -p, --playlist Downloads a saved playlist from your account -ls, --liked-songs Downloads all the liked songs from your account -Special hardcoded options: +Options that can be configured in zs_config.json: ROOT_PATH Change this path if you don't like the default directory where ZSpotify saves the music ROOT_PODCAST_PATH Change this path if you don't like the default directory where ZSpotify saves the podcasts - SKIP_EXISTING_FILES Set this to False if you want ZSpotify to overwrite files with the same name rather than skipping the song + SKIP_EXISTING_FILES Set this to false if you want ZSpotify to overwrite files with the same name rather than skipping the song MUSIC_FORMAT Set this to "ogg" if you would rather that format audio over "mp3" - RAW_AUDIO_AS_IS Set this to True to only stream the audio to a file and do no re-encoding or post processing - - FORCE_PREMIUM Set this to True if ZSpotify isn't automatically detecting that you are using a premium account - + RAW_AUDIO_AS_IS Set this to true to only stream the audio to a file and do no re-encoding or post processing + + FORCE_PREMIUM Set this to true if ZSpotify isn't automatically detecting that you are using a premium account + ANTI_BAN_WAIT_TIME Change this setting if the time waited between bulk downloads is too high or low - OVERRIDE_AUTO_WAIT Change this to True if you want to completely disable the wait between songs for faster downloads with the risk of instability + OVERRIDE_AUTO_WAIT Change this to true if you want to completely disable the wait between songs for faster downloads with the risk of instability ``` ### Will my account get banned if I use this tool? Currently no user has reported their account getting banned after using ZSpotify. @@ -56,11 +56,14 @@ This isn't to say _you_ won't get banned as it is technically againt Spotify's T **Use ZSpotify at your own risk**, the developers of ZSpotify are not responsible if your account gets banned. ## **Changelog:** +**v2.1 (Oct 2021):** +- Moved configuration from hard-coded values to separate zs_config.json file + **v2.0 (22 Oct 2021):** - Added progress bar for downloads. - Added multi-select support for all results when searching. - Added GPLv3 Licence. -- Changed welcome banner and removed unnecessary debug print statments. +- Changed welcome banner and removed unnecessary debug print statements. **v1.9 (22 Oct 2021):** - Added Gitea mirror for when the Spotify Glowies come to DMCA the shit out of this. diff --git a/zs_config.json b/zs_config.json new file mode 100644 index 0000000..87f091d --- /dev/null +++ b/zs_config.json @@ -0,0 +1,11 @@ +{ + "ROOT_PATH": "ZSpotify Music/", + "ROOT_PODCAST_PATH": "ZSpotify Podcasts/", + "SKIP_EXISTING_FILES": true, + "MUSIC_FORMAT": "mp3", + "RAW_AUDIO_AS_IS": false, + "FORCE_PREMIUM": false, + "ANTI_BAN_WAIT_TIME": 1, + "OVERRIDE_AUTO_WAIT": false, + "CHUNK_SIZE": 50000 +} \ No newline at end of file diff --git a/zspotify.py b/zspotify.py index a91e11a..f041f91 100755 --- a/zspotify.py +++ b/zspotify.py @@ -27,22 +27,9 @@ from tqdm import tqdm SESSION: Session = None sanitize = ["\\", "/", ":", "*", "?", "'", "<", ">", '"'] -# Hardcoded variables that adjust the core functionality of ZSpotify -ROOT_PATH = "ZSpotify Music/" -ROOT_PODCAST_PATH = "ZSpotify Podcasts/" - -SKIP_EXISTING_FILES = True - -MUSIC_FORMAT = "mp3" # or "ogg" -RAW_AUDIO_AS_IS = False # set to True if you wish to just save the raw audio - -FORCE_PREMIUM = False # set to True if not detecting your premium account automatically - -# This is how many seconds ZSpotify waits between downloading tracks so spotify doesn't get out the ban hammer -ANTI_BAN_WAIT_TIME = 1 -# Set this to True to not wait at all between tracks and just go balls to the wall -OVERRIDE_AUTO_WAIT = False -CHUNK_SIZE = 50000 +# user-customizable variables that adjust the core functionality of ZSpotify +with open('zs_config.json') as config_file: + ZS_CONFIG = json.load(config_file) # miscellaneous functions for general use @@ -308,8 +295,6 @@ def get_show_episodes(access_token, show_id_str): def download_episode(episode_id_str): - global ROOT_PODCAST_PATH, MUSIC_FORMAT - podcast_name, episode_name = get_episode_info(episode_id_str) extra_paths = podcast_name + "/" @@ -325,22 +310,21 @@ def download_episode(episode_id_str): # print("### DOWNLOADING '" + podcast_name + " - " + # episode_name + "' - THIS MAY TAKE A WHILE ###") - if not os.path.isdir(ROOT_PODCAST_PATH + extra_paths): - os.makedirs(ROOT_PODCAST_PATH + extra_paths) + os.makedirs(ZS_CONFIG["ROOT_PODCAST_PATH"] + extra_paths, exist_ok=True) total_size = stream.input_stream.size - with open(ROOT_PODCAST_PATH + extra_paths + filename + ".wav", 'wb') as file, tqdm( + with open(ZS_CONFIG["ROOT_PODCAST_PATH"] + extra_paths + filename + ".wav", 'wb') as file, tqdm( desc=filename, total=total_size, unit='B', unit_scale=True, unit_divisor=1024 ) as bar: - for _ in range(int(total_size / CHUNK_SIZE) + 1): + for _ in range(int(total_size / ZS_CONFIG["CHUNK_SIZE"]) + 1): bar.update(file.write( - stream.input_stream.stream().read(CHUNK_SIZE))) + stream.input_stream.stream().read(ZS_CONFIG["CHUNK_SIZE"]))) - # convert_audio_format(ROOT_PODCAST_PATH + + # convert_audio_format(ZS_CONFIG["ROOT_PODCAST_PATH"] + # extra_paths + filename + ".wav") # related functions that do stuff with the spotify API @@ -458,22 +442,20 @@ def get_song_info(song_id): def check_premium(): """ If user has spotify premium return true """ - global FORCE_PREMIUM - return bool((SESSION.get_user_attribute("type") == "premium") or FORCE_PREMIUM) + return bool((SESSION.get_user_attribute("type") == "premium") or ZS_CONFIG["FORCE_PREMIUM"]) # Functions directly related to modifying the downloaded audio and its metadata def convert_audio_format(filename): """ Converts raw audio into playable mp3 or ogg vorbis """ - global MUSIC_FORMAT - # print("### CONVERTING TO " + MUSIC_FORMAT.upper() + " ###") + # print("### CONVERTING TO " + ZS_CONFIG["MUSIC_FORMAT"].upper() + " ###") raw_audio = AudioSegment.from_file(filename, format="ogg", frame_rate=44100, channels=2, sample_width=2) if QUALITY == AudioQuality.VERY_HIGH: bitrate = "320k" else: bitrate = "160k" - raw_audio.export(filename, format=MUSIC_FORMAT, bitrate=bitrate) + raw_audio.export(filename, format=ZS_CONFIG["MUSIC_FORMAT"], bitrate=bitrate) def set_audio_tags(filename, artists, name, album_name, release_year, disc_number, track_number): @@ -620,7 +602,6 @@ def get_saved_tracks(access_token): # Functions directly related to downloading stuff def download_track(track_id_str: str, extra_paths="", prefix=False, prefix_value='', disable_progressbar=False): """ Downloads raw song audio from Spotify """ - global ROOT_PATH, SKIP_EXISTING_FILES, MUSIC_FORMAT, RAW_AUDIO_AS_IS, ANTI_BAN_WAIT_TIME, OVERRIDE_AUTO_WAIT try: artists, album_name, name, image_url, release_year, disc_number, track_number, scraped_song_id, is_playable = get_song_info( track_id_str) @@ -629,8 +610,8 @@ def download_track(track_id_str: str, extra_paths="", prefix=False, prefix_value if prefix: song_name = f'{prefix_value.zfill(2)}-{song_name}' if prefix_value.isdigit( ) else f'{prefix_value}-{song_name}' - filename = os.path.join(ROOT_PATH, extra_paths, - song_name + '.' + MUSIC_FORMAT) + filename = os.path.join(ZS_CONFIG["ROOT_PATH"], extra_paths, + song_name + '.' + ZS_CONFIG["MUSIC_FORMAT"]) except Exception as e: print("### SKIPPING SONG - FAILED TO QUERY METADATA ###") # print(e) @@ -640,7 +621,7 @@ def download_track(track_id_str: str, extra_paths="", prefix=False, prefix_value print("### SKIPPING:", song_name, "(SONG IS UNAVAILABLE) ###") else: - if os.path.isfile(filename) and os.path.getsize(filename) and SKIP_EXISTING_FILES: + if os.path.isfile(filename) and os.path.getsize(filename) and ZS_CONFIG["SKIP_EXISTING_FILES"]: print("### SKIPPING:", song_name, "(SONG ALREADY EXISTS) ###") else: @@ -654,8 +635,7 @@ def download_track(track_id_str: str, extra_paths="", prefix=False, prefix_value track_id, VorbisOnlyAudioQuality(QUALITY), False, None) # print("### DOWNLOADING RAW AUDIO ###") - if not os.path.isdir(ROOT_PATH + extra_paths): - os.makedirs(ROOT_PATH + extra_paths) + os.makedirs(ZS_CONFIG["ROOT_PATH"] + extra_paths, exist_ok=True) total_size = stream.input_stream.size with open(filename, 'wb') as file, tqdm( @@ -666,18 +646,18 @@ def download_track(track_id_str: str, extra_paths="", prefix=False, prefix_value unit_divisor=1024, disable=disable_progressbar ) as bar: - for _ in range(int(total_size / CHUNK_SIZE) + 1): + for _ in range(int(total_size / ZS_CONFIG["CHUNK_SIZE"]) + 1): bar.update(file.write( - stream.input_stream.stream().read(CHUNK_SIZE))) + stream.input_stream.stream().read(ZS_CONFIG["CHUNK_SIZE"]))) - if not RAW_AUDIO_AS_IS: + if not ZS_CONFIG["RAW_AUDIO_AS_IS"]: convert_audio_format(filename) set_audio_tags(filename, artists, name, album_name, release_year, disc_number, track_number) set_music_thumbnail(filename, image_url) - if not OVERRIDE_AUTO_WAIT: - time.sleep(ANTI_BAN_WAIT_TIME) + if not ZS_CONFIG["OVERRIDE_AUTO_WAIT"]: + time.sleep(ZS_CONFIG["ANTI_BAN_WAIT_TIME"]) except: print("### SKIPPING:", song_name, "(GENERAL DOWNLOAD ERROR) ###") @@ -751,9 +731,8 @@ def download_from_user_playlist(): def check_raw(): - global RAW_AUDIO_AS_IS, MUSIC_FORMAT - if RAW_AUDIO_AS_IS: - MUSIC_FORMAT = "wav" + if ZS_CONFIG["RAW_AUDIO_AS_IS"]: + ZS_CONFIG["MUSIC_FORMAT"] = "wav" def main():