From d8c17e2ce9cc57b500915fcbe76722fc690c7011 Mon Sep 17 00:00:00 2001 From: logykk Date: Sat, 12 Feb 2022 20:48:27 +1300 Subject: [PATCH] Zotify 0.6 RC1 --- CHANGELOG.md | 25 ++++++++-- README.md | 60 ++++++++++-------------- requirements.txt | 1 + setup.py | 8 ++-- zotify/__main__.py | 10 ++-- zotify/app.py | 21 +++++---- zotify/config.py | 114 ++++++++++++++++++++++++++++++--------------- zotify/podcast.py | 6 +-- zotify/track.py | 4 -- zotify/utils.py | 15 +++--- zotify/zotify.py | 14 +++--- 11 files changed, 161 insertions(+), 117 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fc66d6..d6ffec3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,14 +2,31 @@ ## v0.6 **General changes** - Switched from os.path to pathlib -- Zotify can now be installed with pip - -`pip install https://gitlab.com/team-zotify/zotify/-/archive/main/zotify-main.zip` -- Zotify can be ran from any directory with `zotify [args]`, you no longer need to prefix `python` in the command. +- Renamed .song_archive to track_archive +- Zotify can now be installed with `pip install https://gitlab.com/team-zotify/zotify/-/archive/main/zotify-main.zip` +- Zotify can be ran from any directory with `zotify [args]`, you no longer need to prefix "python" in the command. +- The -s option now takes search input as a command argument, it will still promt you if no search is given. +- The -ls/--liked-songs option has been shrotened to -l/--liked, - New default config locations: - Windows: `%AppData%\Roaming\Zotify\config.json` - Linux: `~/.config/zotify/config.json` - macOS: `~/Library/Application Support/Zotify/config.json` - - You can still use `--config-location` to specify a local config file. + - Other/Undetected: `.zotify/config.json` + - You can still use `--config-location` to specify a different location. +- New default config locations: + - Windows: `%AppData%\Roaming\Zotify\credentials.json` + - Linux: `~/.local/share/zotify/credentials.json` + - macOS: `~/Library/Application Support/Zotify/credentials.json` + - Other/Undetected: `.zotify/credentials.json` + - You can still use `--credentials-location` to specify a different file. +- New default music and podcast locations: + - Windows: `C:\Users\\Music\Zotify Music\` & `C:\Users\\Music\Zotify Podcasts\` + - Linux & macOS: `~/Music/Zotify Music/` & `~/Music/Zotify Podcasts/` + - Other/Undetected: `./Zotify Music/` & `./Zotify Podcasts/` + - You can still use `--root-path` and `--root-podcast-path` respectively to specify a differnt location +- Singles are now stored in their own folders +- Fixed default config not loading on first run +- Now shows asterisks when entering password **Docker** - Dockerfile is currently broken, it will be fixed soon. \ diff --git a/README.md b/README.md index 9f7cd95..297f179 100644 --- a/README.md +++ b/README.md @@ -3,66 +3,58 @@ ### A music and podcast downloader needing only a python interpreter and ffmpeg.

- +

-[Discord Server](https://discord.gg/XDYsFRTUjE) - [NotABug Mirror](https://notabug.org/Zotify/zotify) +[Discord Server](https://discord.gg/XDYsFRTUjE) + +### Install ``` -Requirements: - -Binaries +Dependencies: - Python 3.9 or greater - ffmpeg* -- Git** -Python packages: - -- pip install -r requirements.txt +Installation: +python -m pip install https://gitlab.com/team-zotify/zotify/-/archive/main/zotify-main.zip ``` -\*ffmpeg can be installed via apt for Debian-based distros or by downloading the binaries from [ffmpeg.org](https://ffmpeg.org) and placing them in your %PATH% in Windows. Mac users can install it with [Homebrew](https://brew.sh) by running `brew install ffmpeg`. +\*Windows users can download the binaries from [ffmpeg.org](https://ffmpeg.org) and add them to %PATH%. Mac users can install it via [Homebrew](https://brew.sh) by running `brew install ffmpeg`. Linux users should already know how to install ffmpeg, I don't want to add instructions for every package manager. -\*\*Git can be installed via apt for Debian-based distros or by downloading the binaries from [git-scm.com](https://git-scm.com/download/win) for Windows. - -### Command line usage: +### Command line usage ``` Basic command line usage: zotify Downloads the track, album, playlist or podcast episode specified as a command line argument. If an artist url is given, all albums by specified artist will be downloaded. Can take multiple urls. -Different usage modes: - (nothing) Download the tracks/alumbs/playlists URLs from the parameter - -d, --download Download all tracks/alumbs/playlists URLs from the specified file - -p, --playlist Downloads a saved playlist from your account - -ls, --liked-songs Downloads all the liked songs from your account - -s, --search Loads search prompt to find then download a specific track, album or playlist - -Extra command line options: - -ns, --no-splash Suppress the splash screen when loading. - --config-location Use a different config.json. +Basic options: + (nothing) Download the tracks/alumbs/playlists URLs from the parameter + -d, --download Download all tracks/alumbs/playlists URLs from the specified file + -p, --playlist Downloads a saved playlist from your account + -l, --liked Downloads all the liked songs from your account + -s, --search Searches for specified track, album, artist or playlist, loads search prompt if none are given. ``` -### Options: +### Options All these options can either be configured in the config or via the commandline, in case of both the commandline-option has higher priority. Be aware you have to set boolean values in the commandline like this: `--download-real-time=True` | Key (config) | commandline parameter | Description |------------------------------|----------------------------------|---------------------------------------------------------------------| -| ROOT_PATH | --root-path | directory where Zotify saves the music -| ROOT_PODCAST_PATH | --root-podcast-path | directory where Zotify saves the podcasts +| ROOT_PATH | --root-path | directory where Zotify saves music +| ROOT_PODCAST_PATH | --root-podcast-path | directory where Zotify saves podcasts | SKIP_EXISTING_FILES | --skip-existing-files | Skip songs with the same name -| SKIP_PREVIOUSLY_DOWNLOADED | --skip-previously-downloaded | Create a .song_archive file and skip previously downloaded songs +| SKIP_PREVIOUSLY_DOWNLOADED | --skip-previously-downloaded | Use a song_archive file to skip previously downloaded songs | DOWNLOAD_FORMAT | --download-format | The download audio format (aac, fdk_aac, m4a, mp3, ogg, opus, vorbis) | FORCE_PREMIUM | --force-premium | Force the use of high quality downloads (only with premium accounts) | ANTI_BAN_WAIT_TIME | --anti-ban-wait-time | The wait time between bulk downloads | OVERRIDE_AUTO_WAIT | --override-auto-wait | Totally disable wait time between songs with the risk of instability -| CHUNK_SIZE | --chunk-size | chunk size for downloading -| SPLIT_ALBUM_DISCS | --split-album-discs | split downloaded albums by disc -| DOWNLOAD_REAL_TIME | --download-real-time | only downloads songs as fast as they would be played, can prevent account bans +| CHUNK_SIZE | --chunk-size | Chunk size for downloading +| SPLIT_ALBUM_DISCS | --split-album-discs | Saves each disk in its own folder +| DOWNLOAD_REAL_TIME | --download-real-time | Downloads songs as fast as they would be played, should prevent account bans. | LANGUAGE | --language | Language for spotify metadata | BITRATE | --bitrate | Overwrite the bitrate for ffmpeg encoding | SONG_ARCHIVE | --song-archive | The song_archive file for SKIP_PREVIOUSLY_DOWNLOADED @@ -75,7 +67,7 @@ Be aware you have to set boolean values in the commandline like this: `--downloa | PRINT_DOWNLOADS | --print-downloads | Print messages when a song is finished downloading | TEMP_DOWNLOAD_DIR | --temp-download-dir | Download tracks to a temporary directory first -### Output format: +### Output format With the option `OUTPUT` (or the commandline parameter `--output`) you can specify the output location and format. The value is relative to the `ROOT_PATH`/`ROOT_PODCAST_PATH` directory and can contain the following placeholder: @@ -100,7 +92,7 @@ Example values could be: ~~~~ {playlist}/{artist} - {song_name}.{ext} {playlist}/{playlist_num} - {artist} - {song_name}.{ext} -Liked Songs/{artist} - {song_name}.{ext} +Bangers/{artist} - {song_name}.{ext} {artist} - {song_name}.{ext} {artist}/{album}/{album_num} - {artist} - {song_name}.{ext} /home/user/downloads/{artist} - {song_name} [{id}].{ext} @@ -135,7 +127,3 @@ Please refer to [CONTRIBUTING](CONTRIBUTING.md) ### Changelog Please refer to [CHANGELOG](CHANGELOG.md) - -### Common Errors - -Please refer to [COMMON_ERRORS](COMMON_ERRORS.md) diff --git a/requirements.txt b/requirements.txt index dbb530a..8bd5229 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,5 +3,6 @@ https://github.com/kokarare1212/librespot-python/archive/refs/heads/rewrite.zip music_tag Pillow protobuf +pwinput tabulate tqdm diff --git a/setup.py b/setup.py index e3abc2e..fbcda8b 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,10 @@ -import pathlib +from pathlib import Path from distutils.core import setup from setuptools import setup, find_packages # The directory containing this file -HERE = pathlib.Path(__file__).parent +HERE = Path(__file__).parent # The text of the README file README = (HERE / "README.md").read_text() @@ -13,7 +13,7 @@ README = (HERE / "README.md").read_text() setup( name="zotify", version="0.6.0", - author="Zotify", + author="Zotify Contributors", description="A music and podcast downloader.", long_description=README, long_description_content_type="text/markdown", @@ -30,6 +30,6 @@ setup( "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", ], - install_requires=['ffmpy', 'music_tag', 'Pillow', 'protobuf', 'tabulate', 'tqdm', + install_requires=['ffmpy', 'music_tag', 'Pillow', 'protobuf', 'pwinput', 'tabulate', 'tqdm', 'librespot @ https://github.com/kokarare1212/librespot-python/archive/refs/heads/rewrite.zip'], ) diff --git a/zotify/__main__.py b/zotify/__main__.py index 1837bd8..e92c906 100644 --- a/zotify/__main__.py +++ b/zotify/__main__.py @@ -18,7 +18,7 @@ def main(): help='Suppress the splash screen when loading.') parser.add_argument('--config-location', type=str, - help='Specify the json config location') + help='Specify the zconfig.json location') group = parser.add_mutually_exclusive_group(required=True) group.add_argument('urls', type=str, @@ -26,7 +26,7 @@ def main(): default='', nargs='*', help='Downloads the track, album, playlist, podcast episode, or all albums by an artist from a url. Can take multiple urls.') - group.add_argument('-ls', '--liked-songs', + group.add_argument('-l', '--liked', dest='liked_songs', action='store_true', help='Downloads all the liked songs from your account.') @@ -34,8 +34,9 @@ def main(): action='store_true', help='Downloads a saved playlist from your account.') group.add_argument('-s', '--search', - dest='search_spotify', - action='store_true', + type=str, + nargs='?', + const=' ', help='Loads search prompt to find then download a specific track, album or playlist') group.add_argument('-d', '--download', type=str, @@ -52,5 +53,6 @@ def main(): args = parser.parse_args() args.func(args) + if __name__ == '__main__': main() diff --git a/zotify/app.py b/zotify/app.py index d32f30c..7ba1931 100644 --- a/zotify/app.py +++ b/zotify/app.py @@ -1,6 +1,5 @@ from librespot.audio.decoders import AudioQuality from tabulate import tabulate -#import os from pathlib import Path from zotify.album import download_album, download_artist_albums @@ -23,10 +22,10 @@ def client(args) -> None: Printer.print(PrintChannel.SPLASH, splash()) if Zotify.check_premium(): - Printer.print(PrintChannel.WARNINGS, '[ DETECTED PREMIUM ACCOUNT - USING VERY_HIGH QUALITY ]\n\n') + Printer.print(PrintChannel.WARNINGS, '[ DETECTED PREMIUM ACCOUNT - USING VERY_HIGH QUALITY ]\n') Zotify.DOWNLOAD_QUALITY = AudioQuality.VERY_HIGH else: - Printer.print(PrintChannel.WARNINGS, '[ DETECTED FREE ACCOUNT - USING HIGH QUALITY ]\n\n') + Printer.print(PrintChannel.WARNINGS, '[ DETECTED FREE ACCOUNT - USING HIGH QUALITY ]\n') Zotify.DOWNLOAD_QUALITY = AudioQuality.HIGH if args.download: @@ -42,7 +41,8 @@ def client(args) -> None: Printer.print(PrintChannel.ERRORS, f'File {filename} not found.\n') if args.urls: - download_from_urls(args.urls) + if len(args.urls) > 0: + download_from_urls(args.urls) if args.playlist: download_from_user_playlist() @@ -54,13 +54,14 @@ def client(args) -> None: else: download_track('liked', song[TRACK][ID]) - if args.search_spotify: - search_text = '' - while len(search_text) == 0: - search_text = input('Enter search or URL: ') + if args.search: + if args.search == ' ': + search_text = '' + while len(search_text) == 0: + search_text = input('Enter search or URL: ') - if not download_from_urls([search_text]): - search(search_text) + if not download_from_urls([args.search]): + search(args.search) def download_from_urls(urls: list[str]) -> bool: """ Downloads from a list of urls """ diff --git a/zotify/config.py b/zotify/config.py index c51f700..fec8d90 100644 --- a/zotify/config.py +++ b/zotify/config.py @@ -17,7 +17,7 @@ SPLIT_ALBUM_DISCS = 'SPLIT_ALBUM_DISCS' DOWNLOAD_REAL_TIME = 'DOWNLOAD_REAL_TIME' LANGUAGE = 'LANGUAGE' BITRATE = 'BITRATE' -SONG_ARCHIVE = 'SONG_ARCHIVE' +TRACK_ARCHIVE = 'TRACK_ARCHIVE' CREDENTIALS_LOCATION = 'CREDENTIALS_LOCATION' OUTPUT = 'OUTPUT' PRINT_SPLASH = 'PRINT_SPLASH' @@ -32,42 +32,43 @@ MD_GENREDELIMITER = 'MD_GENREDELIMITER' PRINT_PROGRESS_INFO = 'PRINT_PROGRESS_INFO' PRINT_WARNINGS = 'PRINT_WARNINGS' RETRY_ATTEMPTS = 'RETRY_ATTEMPTS' +CONFIG_VERSION = 'CONFIG_VERSION' CONFIG_VALUES = { - ROOT_PATH: { 'default': './Zotify Music/', 'type': str, 'arg': '--root-path' }, - ROOT_PODCAST_PATH: { 'default': './Zotify Podcasts/', 'type': str, 'arg': '--root-podcast-path' }, - SKIP_EXISTING_FILES: { 'default': 'True', 'type': bool, 'arg': '--skip-existing-files' }, - SKIP_PREVIOUSLY_DOWNLOADED: { 'default': 'False', 'type': bool, 'arg': '--skip-previously-downloaded' }, - RETRY_ATTEMPTS: { 'default': '5', 'type': int, 'arg': '--retry-attemps' }, - DOWNLOAD_FORMAT: { 'default': 'ogg', 'type': str, 'arg': '--download-format' }, - FORCE_PREMIUM: { 'default': 'False', 'type': bool, 'arg': '--force-premium' }, - ANTI_BAN_WAIT_TIME: { 'default': '1', 'type': int, 'arg': '--anti-ban-wait-time' }, - OVERRIDE_AUTO_WAIT: { 'default': 'False', 'type': bool, 'arg': '--override-auto-wait' }, - CHUNK_SIZE: { 'default': '50000', 'type': int, 'arg': '--chunk-size' }, - SPLIT_ALBUM_DISCS: { 'default': 'False', 'type': bool, 'arg': '--split-album-discs' }, - DOWNLOAD_REAL_TIME: { 'default': 'False', 'type': bool, 'arg': '--download-real-time' }, - LANGUAGE: { 'default': 'en', 'type': str, 'arg': '--language' }, - BITRATE: { 'default': '', 'type': str, 'arg': '--bitrate' }, - SONG_ARCHIVE: { 'default': '.song_archive', 'type': str, 'arg': '--song-archive' }, - CREDENTIALS_LOCATION: { 'default': 'credentials.json', 'type': str, 'arg': '--credentials-location' }, - OUTPUT: { 'default': '', 'type': str, 'arg': '--output' }, - PRINT_SPLASH: { 'default': 'False', 'type': bool, 'arg': '--print-splash' }, - PRINT_SKIPS: { 'default': 'True', 'type': bool, 'arg': '--print-skips' }, - 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_PROGRESS_INFO: { 'default': 'True', 'type': bool, 'arg': '--print-progress-info' }, - PRINT_WARNINGS: { 'default': 'True', 'type': bool, 'arg': '--print-warnings' }, - 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' } + ROOT_PATH: { 'default': '', 'type': str, 'arg': '--root-path' }, + ROOT_PODCAST_PATH: { 'default': '', 'type': str, 'arg': '--root-podcast-path' }, + SKIP_EXISTING_FILES: { 'default': 'True', 'type': bool, 'arg': '--skip-existing-files' }, + SKIP_PREVIOUSLY_DOWNLOADED: { 'default': 'False', 'type': bool, 'arg': '--skip-previously-downloaded' }, + RETRY_ATTEMPTS: { 'default': '5', 'type': int, 'arg': '--retry-attemps' }, + DOWNLOAD_FORMAT: { 'default': 'ogg', 'type': str, 'arg': '--download-format' }, + FORCE_PREMIUM: { 'default': 'False', 'type': bool, 'arg': '--force-premium' }, + ANTI_BAN_WAIT_TIME: { 'default': '1', 'type': int, 'arg': '--anti-ban-wait-time' }, + OVERRIDE_AUTO_WAIT: { 'default': 'False', 'type': bool, 'arg': '--override-auto-wait' }, + CHUNK_SIZE: { 'default': '50000', 'type': int, 'arg': '--chunk-size' }, + SPLIT_ALBUM_DISCS: { 'default': 'False', 'type': bool, 'arg': '--split-album-discs' }, + DOWNLOAD_REAL_TIME: { 'default': 'False', 'type': bool, 'arg': '--download-real-time' }, + LANGUAGE: { 'default': 'en', 'type': str, 'arg': '--language' }, + BITRATE: { 'default': '', 'type': str, 'arg': '--bitrate' }, + TRACK_ARCHIVE: { 'default': '', 'type': str, 'arg': '--track-archive' }, + CREDENTIALS_LOCATION: { 'default': '', 'type': str, 'arg': '--credentials-location' }, + OUTPUT: { 'default': '', 'type': str, 'arg': '--output' }, + PRINT_SPLASH: { 'default': 'False', 'type': bool, 'arg': '--print-splash' }, + PRINT_SKIPS: { 'default': 'True', 'type': bool, 'arg': '--print-skips' }, + 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_PROGRESS_INFO: { 'default': 'True', 'type': bool, 'arg': '--print-progress-info' }, + PRINT_WARNINGS: { 'default': 'True', 'type': bool, 'arg': '--print-warnings' }, + 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}' OUTPUT_DEFAULT_PLAYLIST_EXT = '{playlist}/{playlist_num} - {artist} - {song_name}.{ext}' OUTPUT_DEFAULT_LIKED_SONGS = 'Liked Songs/{artist} - {song_name}.{ext}' -OUTPUT_DEFAULT_SINGLE = '{artist} - {song_name}.{ext}' +OUTPUT_DEFAULT_SINGLE = '{artist} - {song_name}/{artist} - {song_name}.{ext}' OUTPUT_DEFAULT_ALBUM = '{artist}/{album}/{album_num} - {artist} - {song_name}.{ext}' @@ -81,7 +82,10 @@ class Config: 'linux': Path.home() / '.config/zotify', 'darwin': Path.home() / 'Library/Application Support/Zotify' } - config_fp = system_paths[sys.platform] / 'config.json' + if sys.platform not in system_paths: + config_fp = Path.cwd() / '.zotify/config.json' + else: + config_fp = system_paths[sys.platform] / 'config.json' if args.config_location: config_fp = args.config_location @@ -143,13 +147,21 @@ class Config: @classmethod def get_root_path(cls) -> str: - # return PurePath(Path.cwd()).joinpath(cls.get(ROOT_PATH)) - return PurePath(Path(cls.get(ROOT_PATH)).expanduser()) + if cls.get(ROOT_PATH) == '': + root_path = PurePath(Path.home() / 'Music/Zotify Music/') + else: + root_path = PurePath(Path(cls.get(ROOT_PATH)).expanduser()) + Path(root_path).mkdir(parents=True, exist_ok=True) + return root_path @classmethod def get_root_podcast_path(cls) -> str: - # return PurePath(Path.cwd()).joinpath(cls.get(ROOT_PODCAST_PATH)) - return PurePath(Path(cls.get(ROOT_PODCAST_PATH)).expanduser()) + if cls.get(ROOT_PODCAST_PATH) == '': + root_podcast_path = PurePath(Path.home() / 'Music/Zotify Podcasts/') + else: + root_podcast_path = PurePath(Path(cls.get(ROOT_PODCAST_PATH)).expanduser()) + Path(root_podcast_path).mkdir(parents=True, exist_ok=True) + return root_podcast_path @classmethod def get_skip_existing_files(cls) -> bool: @@ -196,12 +208,38 @@ class Config: return cls.get(BITRATE) @classmethod - def get_song_archive(cls) -> str: - return PurePath(cls.get_root_path()).joinpath(cls.get(SONG_ARCHIVE)) + def get_track_archive(cls) -> str: + if cls.get(TRACK_ARCHIVE) == '': + system_paths = { + 'win32': Path.home() / 'AppData/Roaming/Zotify', + 'linux': Path.home() / '.local/share/zotify', + 'darwin': Path.home() / 'Library/Application Support/Zotify' + } + if sys.platform not in system_paths: + track_archive = PurePath(Path.cwd() / '.zotify/track_archive') + else: + track_archive = PurePath(system_paths[sys.platform] / 'track_archive') + else: + track_archive = PurePath(Path(cls.get(TRACK_ARCHIVE)).expanduser()) + Path(track_archive.parent).mkdir(parents=True, exist_ok=True) + return track_archive @classmethod def get_credentials_location(cls) -> str: - return PurePath(Path.cwd()).joinpath(cls.get(CREDENTIALS_LOCATION)) + if cls.get(CREDENTIALS_LOCATION) == '': + system_paths = { + 'win32': Path.home() / 'AppData/Roaming/Zotify', + 'linux': Path.home() / '.local/share/zotify', + 'darwin': Path.home() / 'Library/Application Support/Zotify' + } + if sys.platform not in system_paths: + credentials_location = PurePath(Path.cwd() / '.zotify/credentials.json') + else: + credentials_location = PurePath(system_paths[sys.platform] / 'credentials.json') + else: + credentials_location = PurePath(Path.cwd()).joinpath(cls.get(CREDENTIALS_LOCATION)) + Path(credentials_location.parent).mkdir(parents=True, exist_ok=True) + return credentials_location @classmethod def get_temp_download_dir(cls) -> str: diff --git a/zotify/podcast.py b/zotify/podcast.py index 63863cf..67f1b41 100644 --- a/zotify/podcast.py +++ b/zotify/podcast.py @@ -7,7 +7,7 @@ from librespot.metadata import EpisodeId from zotify.const import ERROR, ID, ITEMS, NAME, SHOW, DURATION_MS from zotify.termoutput import PrintChannel, Printer -from zotify.utils import create_download_directory, fix_filename, convert_audio_format +from zotify.utils import create_download_directory, fix_filename from zotify.zotify import Zotify from zotify.loader import Loader @@ -24,7 +24,7 @@ def get_episode_info(episode_id_str) -> Tuple[Optional[str], Optional[str]]: duration_ms = info[DURATION_MS] if ERROR in info: return None, None - return fix_filename(info[SHOW][NAME]), duration_ms, fix_filename(info[NAME]) + return fix_filename(info[SHOW][NAME]), duration_ms, fix_filename(info[NAME]) def get_show_episodes(show_id_str) -> list: @@ -47,7 +47,6 @@ def get_show_episodes(show_id_str) -> list: def download_podcast_directly(url, filename): import functools - # import pathlib import shutil import requests from tqdm.auto import tqdm @@ -59,7 +58,6 @@ def download_podcast_directly(url, filename): f"Request to {url} returned status code {r.status_code}") file_size = int(r.headers.get('Content-Length', 0)) - # path = pathlib.Path(filename).expanduser().resolve() path = Path(filename).expanduser().resolve() path.parent.mkdir(parents=True, exist_ok=True) diff --git a/zotify/track.py b/zotify/track.py index b6b0019..a3fcef4 100644 --- a/zotify/track.py +++ b/zotify/track.py @@ -1,4 +1,3 @@ -# import os from pathlib import Path, PurePath import re import time @@ -152,8 +151,6 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba if not check_id and check_name: c = len([file for file in Path(filedir).iterdir() if re.search(f'^{filename}_', str(file))]) + 1 - # fname = os.path.splitext(os.path.basename(filename))[0] - # ext = os.path.splitext(os.path.basename(filename))[1] fname = PurePath(PurePath(filename).name).parent ext = PurePath(PurePath(filename).name).suffix @@ -252,7 +249,6 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba def convert_audio_format(filename) -> None: """ Converts raw audio into playable file """ - # temp_filename = f'{os.path.splitext(filename)[0]}.tmp' temp_filename = f'{PurePath(filename).parent}.tmp' Path(filename).replace(temp_filename) diff --git a/zotify/utils.py b/zotify/utils.py index 0d051d6..05084c2 100644 --- a/zotify/utils.py +++ b/zotify/utils.py @@ -36,7 +36,7 @@ def get_previously_downloaded() -> List[str]: """ Returns list of all time downloaded songs """ ids = [] - archive_path = Zotify.CONFIG.get_song_archive() + archive_path = Zotify.CONFIG.get_track_archive() if Path(archive_path).exists(): with open(archive_path, 'r', encoding='utf-8') as f: @@ -48,7 +48,7 @@ def get_previously_downloaded() -> List[str]: def add_to_archive(song_id: str, filename: str, author_name: str, song_name: str) -> None: """ Adds song id to all time installed songs archive """ - archive_path = Zotify.CONFIG.get_song_archive() + archive_path = Zotify.CONFIG.get_track_archive() if Path(archive_path).exists(): with open(archive_path, 'a', encoding='utf-8') as file: @@ -109,11 +109,12 @@ def split_input(selection) -> List[str]: def splash() -> str: """ Displays splash screen """ return """ -███████ ██████ ████████ ██ ███████ ██ ██ - ███ ██ ██ ██ ██ ██ ██ ██ - ███ ██ ██ ██ ██ █████ ████ - ███ ██ ██ ██ ██ ██ ██ -███████ ██████ ██ ██ ██ ██ +███████╗ ██████╗ ████████╗██╗███████╗██╗ ██╗ +╚══███╔╝██╔═══██╗╚══██╔══╝██║██╔════╝╚██╗ ██╔╝ + ███╔╝ ██║ ██║ ██║ ██║█████╗ ╚████╔╝ + ███╔╝ ██║ ██║ ██║ ██║██╔══╝ ╚██╔╝ +███████╗╚██████╔╝ ██║ ██║██║ ██║ +╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ """ diff --git a/zotify/zotify.py b/zotify/zotify.py index d963359..dd40d6d 100644 --- a/zotify/zotify.py +++ b/zotify/zotify.py @@ -1,7 +1,5 @@ -import os -import os.path from pathlib import Path -from getpass import getpass +from pwinput import pwinput import time import requests from librespot.audio.decoders import VorbisOnlyAudioQuality @@ -29,7 +27,8 @@ class Zotify: if Path(cred_location).is_file(): try: - cls.SESSION = Session.Builder().stored_file(cred_location).create() + conf = Session.Configuration.Builder().set_store_credentials(False).build() + cls.SESSION = Session.Builder(conf).stored_file(cred_location).create() return except RuntimeError: pass @@ -37,7 +36,7 @@ class Zotify: user_name = '' while len(user_name) == 0: user_name = input('Username: ') - password = getpass() + password = pwinput(prompt='Password: ', mask='*') try: conf = Session.Configuration.Builder().set_stored_credential_file(cred_location).build() cls.SESSION = Session.Builder(conf).user_pass(user_name, password).create() @@ -80,7 +79,10 @@ class Zotify: headers = cls.get_auth_header() response = requests.get(url, headers=headers) responsetext = response.text - responsejson = response.json() + try: + responsejson = response.json() + except requests.exceptions.JSONDecodeError: + responsejson = {} if 'error' in responsejson: if tryCount < (cls.CONFIG.get_retry_attempts() - 1):