From 6c23d2efde2236ff80a42140eb3c714c7795ae7e Mon Sep 17 00:00:00 2001 From: azumukupoe <31339007+azumukupoe@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:14:58 +0900 Subject: [PATCH 01/19] Added an option to set maximum filename length --- README.md | 1 + zotify/config.py | 6 ++++++ zotify/utils.py | 15 +++++++++++---- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e54b8222..790ccb41 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ Be aware you have to set boolean values in the commandline like this: `--downloa | SONG_ARCHIVE | --song-archive | | The song_archive file for SKIP_PREVIOUSLY_DOWNLOADED | ROOT_PATH | --root-path | | Directory where Zotify saves music | ROOT_PODCAST_PATH | --root-podcast-path | | Directory where Zotify saves podcasts +| MAX_FILENAME_LENGTH | --max-filename-length | 255 | Maximum filename length | SPLIT_ALBUM_DISCS | --split-album-discs | False | Saves each disk in its own folder | DOWNLOAD_LYRICS | --download-lyrics | True | Downloads synced lyrics in .lrc format, uses unsynced as fallback. | MD_ALLGENRES | --md-allgenres | False | Save all relevant genres in metadata diff --git a/zotify/config.py b/zotify/config.py index 802d5ced..340dbf72 100644 --- a/zotify/config.py +++ b/zotify/config.py @@ -36,6 +36,7 @@ RETRY_ATTEMPTS = 'RETRY_ATTEMPTS' CONFIG_VERSION = 'CONFIG_VERSION' DOWNLOAD_LYRICS = 'DOWNLOAD_LYRICS' +MAX_FILENAME_LENGTH = 'MAX_FILENAME_LENGTH' CONFIG_VALUES = { SAVE_CREDENTIALS: { 'default': 'True', 'type': bool, 'arg': '--save-credentials' }, @@ -44,6 +45,7 @@ SONG_ARCHIVE: { 'default': '', 'type': str, 'arg': '--song-archive' }, ROOT_PATH: { 'default': '', 'type': str, 'arg': '--root-path' }, ROOT_PODCAST_PATH: { 'default': '', 'type': str, 'arg': '--root-podcast-path' }, + MAX_FILENAME_LENGTH: { 'default': '255', 'type': int, 'arg': '--max-filename-length' }, SPLIT_ALBUM_DISCS: { 'default': 'False', 'type': bool, 'arg': '--split-album-discs' }, DOWNLOAD_LYRICS: { 'default': 'True', 'type': bool, 'arg': '--download-lyrics' }, MD_SAVE_GENRES: { 'default': 'False', 'type': bool, 'arg': '--md-save-genres' }, @@ -308,3 +310,7 @@ def get_output(cls, mode: str) -> str: @classmethod def get_retry_attempts(cls) -> int: return cls.get(RETRY_ATTEMPTS) + + @classmethod + def get_max_filename_length(cls) -> int: + return cls.get(MAX_FILENAME_LENGTH) diff --git a/zotify/utils.py b/zotify/utils.py index dce9fd24..a93f1ed4 100644 --- a/zotify/utils.py +++ b/zotify/utils.py @@ -244,7 +244,7 @@ def regex_input_for_urls(search_input) -> Tuple[str, str, str, str, str, str]: def fix_filename(name): """ - Replace invalid characters on Linux/Windows/MacOS with underscores. + Replace invalid characters on Linux/Windows/MacOS with underscores and truncate if too long. List from https://stackoverflow.com/a/31976060/819417 Trailing spaces & periods are ignored on Windows. >>> fix_filename(" COM1 ") @@ -259,11 +259,18 @@ def fix_filename(name): True """ if platform.system() == WINDOWS_SYSTEM: - return re.sub(r'[/\\:|<>"?*\0-\x1f]|^(AUX|COM[1-9]|CON|LPT[1-9]|NUL|PRN)(?![^.])|^\s|[\s.]$', "_", str(name), flags=re.IGNORECASE) + name = re.sub(r'[/\\:|<>"?*\0-\x1f]|^(AUX|COM[1-9]|CON|LPT[1-9]|NUL|PRN)(?![^.])|^\s|[\s.]$', "_", str(name), flags=re.IGNORECASE) elif platform.system() == LINUX_SYSTEM: - return re.sub(r'[/\0]', "_", str(name)) + name = re.sub(r'[/\0]', "_", str(name)) else: # MacOS - return re.sub(r'[/:\0]', "_", str(name)) + name = re.sub(r'[/:\0]', "_", str(name)) + + max_filename_length = Zotify.CONFIG.get_max_filename_length() + + if len(name) > max_filename_length: + name = name[:max_filename_length] + + return name def fmt_seconds(secs: float) -> str: From e97ba9168d0f54e276a085d115af34ff4266c13d Mon Sep 17 00:00:00 2001 From: azumukupoe <31339007+azumukupoe@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:21:57 +0900 Subject: [PATCH 02/19] Undo edit to a comment. --- zotify/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zotify/utils.py b/zotify/utils.py index a93f1ed4..0138b5b2 100644 --- a/zotify/utils.py +++ b/zotify/utils.py @@ -244,7 +244,7 @@ def regex_input_for_urls(search_input) -> Tuple[str, str, str, str, str, str]: def fix_filename(name): """ - Replace invalid characters on Linux/Windows/MacOS with underscores and truncate if too long. + Replace invalid characters on Linux/Windows/MacOS with underscores. List from https://stackoverflow.com/a/31976060/819417 Trailing spaces & periods are ignored on Windows. >>> fix_filename(" COM1 ") From 175f5ce84469772e41a87051771cd2b3dc2018c6 Mon Sep 17 00:00:00 2001 From: azumukupoe <31339007+azumukupoe@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:12:40 +0900 Subject: [PATCH 03/19] Force track_number to be 2 digit values --- zotify/track.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zotify/track.py b/zotify/track.py index a739a3d4..d1f25c73 100644 --- a/zotify/track.py +++ b/zotify/track.py @@ -169,7 +169,7 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba output_template = output_template.replace("{song_name}", fix_filename(name)) output_template = output_template.replace("{release_year}", fix_filename(release_year)) output_template = output_template.replace("{disc_number}", fix_filename(disc_number)) - output_template = output_template.replace("{track_number}", fix_filename(track_number)) + output_template = output_template.replace("{track_number}", '{:02d}'.format(int(fix_filename(track_number)))) output_template = output_template.replace("{id}", fix_filename(scraped_song_id)) output_template = output_template.replace("{track_id}", fix_filename(track_id)) output_template = output_template.replace("{ext}", ext) From a3b1a4c0aa1123049dc11debb0477cc8b6f6f34c Mon Sep 17 00:00:00 2001 From: azumukupoe <31339007+azumukupoe@users.noreply.github.com> Date: Fri, 5 Jul 2024 19:30:33 +0900 Subject: [PATCH 04/19] Changed artist join character to '/' --- zotify/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zotify/utils.py b/zotify/utils.py index 0138b5b2..756e6aa1 100644 --- a/zotify/utils.py +++ b/zotify/utils.py @@ -142,7 +142,7 @@ def set_audio_tags(filename, artists, genres, name, album_name, release_year, di def conv_artist_format(artists) -> str: """ Returns converted artist format """ - return ', '.join(artists) + return '/'.join(artists) def set_music_thumbnail(filename, image_url) -> None: From a0936bf2fd63adf85c2fa9bc80356e2fd7e8354c Mon Sep 17 00:00:00 2001 From: azumukupoe <31339007+azumukupoe@users.noreply.github.com> Date: Sat, 6 Jul 2024 13:44:34 +0900 Subject: [PATCH 05/19] Added an option to change artist delimiter character --- README.md | 3 ++- zotify/config.py | 8 +++++++- zotify/utils.py | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 790ccb41..a5562390 100644 --- a/README.md +++ b/README.md @@ -66,8 +66,9 @@ Be aware you have to set boolean values in the commandline like this: `--downloa | MAX_FILENAME_LENGTH | --max-filename-length | 255 | Maximum filename length | SPLIT_ALBUM_DISCS | --split-album-discs | False | Saves each disk in its own folder | DOWNLOAD_LYRICS | --download-lyrics | True | Downloads synced lyrics in .lrc format, uses unsynced as fallback. +| MD_ARTISTDELIMITER | --md-artistdelimiter | ', ' | Delimiter character used to split artists in metadata | MD_ALLGENRES | --md-allgenres | False | Save all relevant genres in metadata -| MD_GENREDELIMITER | --md-genredelimiter | , | Delimiter character used to split genres in metadata +| MD_GENREDELIMITER | --md-genredelimiter | ', ' | Delimiter character used to split genres in metadata | DOWNLOAD_FORMAT | --download-format | ogg | The download audio format (aac, fdk_aac, m4a, mp3, ogg, opus, vorbis) | DOWNLOAD_QUALITY | --download-quality | auto | Audio quality of downloaded songs (normal, high, very_high*) | TRANSCODE_BITRATE | --transcode-bitrate | auto | Overwrite the bitrate for ffmpeg encoding diff --git a/zotify/config.py b/zotify/config.py index 340dbf72..f50acbe6 100644 --- a/zotify/config.py +++ b/zotify/config.py @@ -28,6 +28,7 @@ PRINT_DOWNLOADS = 'PRINT_DOWNLOADS' PRINT_API_ERRORS = 'PRINT_API_ERRORS' TEMP_DOWNLOAD_DIR = 'TEMP_DOWNLOAD_DIR' +MD_ARTISTDELIMITER = 'MD_ARTISTDELIMITER' MD_SAVE_GENRES = 'MD_SAVE_GENRES' MD_ALLGENRES = 'MD_ALLGENRES' MD_GENREDELIMITER = 'MD_GENREDELIMITER' @@ -48,9 +49,10 @@ MAX_FILENAME_LENGTH: { 'default': '255', 'type': int, 'arg': '--max-filename-length' }, SPLIT_ALBUM_DISCS: { 'default': 'False', 'type': bool, 'arg': '--split-album-discs' }, DOWNLOAD_LYRICS: { 'default': 'True', 'type': bool, 'arg': '--download-lyrics' }, + MD_ARTISTDELIMITER: { 'default': ', ', 'type': str, 'arg': '--md-artistdelimiter' }, MD_SAVE_GENRES: { 'default': 'False', 'type': bool, 'arg': '--md-save-genres' }, MD_ALLGENRES: { 'default': 'False', 'type': bool, 'arg': '--md-allgenres' }, - MD_GENREDELIMITER: { 'default': ',', 'type': str, 'arg': '--md-genredelimiter' }, + MD_GENREDELIMITER: { 'default': ', ', 'type': str, 'arg': '--md-genredelimiter' }, DOWNLOAD_FORMAT: { 'default': 'ogg', 'type': str, 'arg': '--download-format' }, DOWNLOAD_QUALITY: { 'default': 'auto', 'type': str, 'arg': '--download-quality' }, TRANSCODE_BITRATE: { 'default': 'auto', 'type': str, 'arg': '--transcode-bitrate' }, @@ -263,6 +265,10 @@ def get_temp_download_dir(cls) -> str: return '' return PurePath(cls.get_root_path()).joinpath(cls.get(TEMP_DOWNLOAD_DIR)) + @classmethod + def get_artist_delimiter(cls) -> bool: + return cls.get(MD_ARTISTDELIMITER) + @classmethod def get_save_genres(cls) -> bool: return cls.get(MD_SAVE_GENRES) diff --git a/zotify/utils.py b/zotify/utils.py index 756e6aa1..f483b23a 100644 --- a/zotify/utils.py +++ b/zotify/utils.py @@ -142,7 +142,7 @@ def set_audio_tags(filename, artists, genres, name, album_name, release_year, di def conv_artist_format(artists) -> str: """ Returns converted artist format """ - return '/'.join(artists) + return Zotify.CONFIG.get_artist_delimiter().join(artists) def set_music_thumbnail(filename, image_url) -> None: From 475db486b52f9bd01b73c61044b501e0fdb9963e Mon Sep 17 00:00:00 2001 From: hollis Date: Thu, 4 Jul 2024 13:56:34 +0930 Subject: [PATCH 06/19] grab and populate total number of tracks --- zotify/const.py | 4 ++++ zotify/track.py | 12 +++++++----- zotify/utils.py | 5 +++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/zotify/const.py b/zotify/const.py index e269a903..d50d4006 100644 --- a/zotify/const.py +++ b/zotify/const.py @@ -8,6 +8,8 @@ TRACKNUMBER = 'tracknumber' +TOTALTRACKS = 'totaltracks' + DISCNUMBER = 'discnumber' YEAR = 'year' @@ -58,6 +60,8 @@ TRACK_NUMBER = 'track_number' +TOTAL_TRACKS = 'total_tracks' + DISC_NUMBER = 'disc_number' SHOW = 'show' diff --git a/zotify/track.py b/zotify/track.py index d1f25c73..38e1f919 100644 --- a/zotify/track.py +++ b/zotify/track.py @@ -8,7 +8,7 @@ from librespot.metadata import TrackId import ffmpy -from zotify.const import TRACKS, ALBUM, GENRES, NAME, ITEMS, DISC_NUMBER, TRACK_NUMBER, IS_PLAYABLE, ARTISTS, IMAGES, URL, \ +from zotify.const import TRACKS, ALBUM, GENRES, NAME, ITEMS, DISC_NUMBER, TRACK_NUMBER, TOTAL_TRACKS, IS_PLAYABLE, ARTISTS, IMAGES, URL, \ RELEASE_DATE, ID, TRACKS_URL, FOLLOWED_ARTISTS_URL, SAVED_TRACKS_URL, TRACK_STATS_URL, CODEC_MAP, EXT_MAP, DURATION_MS, \ HREF, ARTISTS, WIDTH from zotify.termoutput import Printer, PrintChannel @@ -46,7 +46,7 @@ def get_followed_artists() -> list: return artists -def get_song_info(song_id) -> Tuple[List[str], List[Any], str, str, Any, Any, Any, Any, Any, Any, int]: +def get_song_info(song_id) -> Tuple[List[str], List[Any], str, str, Any, Any, Any, Any, Any, Any, Any, int]: """ Retrieves metadata for downloaded songs """ with Loader(PrintChannel.PROGRESS_INFO, "Fetching track information..."): (raw, info) = Zotify.invoke_url(f'{TRACKS_URL}?ids={song_id}&market=from_token') @@ -63,6 +63,7 @@ def get_song_info(song_id) -> Tuple[List[str], List[Any], str, str, Any, Any, An name = info[TRACKS][0][NAME] release_year = info[TRACKS][0][ALBUM][RELEASE_DATE].split('-')[0] disc_number = info[TRACKS][0][DISC_NUMBER] + total_tracks = info[TRACKS][0][ALBUM][TOTAL_TRACKS] track_number = info[TRACKS][0][TRACK_NUMBER] scraped_song_id = info[TRACKS][0][ID] is_playable = info[TRACKS][0][IS_PLAYABLE] @@ -74,7 +75,7 @@ def get_song_info(song_id) -> Tuple[List[str], List[Any], str, str, Any, Any, An image = i image_url = image[URL] - return artists, info[TRACKS][0][ARTISTS], album_name, name, image_url, release_year, disc_number, track_number, scraped_song_id, is_playable, duration_ms + return artists, info[TRACKS][0][ARTISTS], album_name, name, image_url, release_year, disc_number, track_number, total_tracks, scraped_song_id, is_playable, duration_ms except Exception as e: raise ValueError(f'Failed to parse TRACKS_URL response: {str(e)}\n{raw}') @@ -155,7 +156,7 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba output_template = Zotify.CONFIG.get_output(mode) (artists, raw_artists, album_name, name, image_url, release_year, disc_number, - track_number, scraped_song_id, is_playable, duration_ms) = get_song_info(track_id) + track_number, total_tracks, scraped_song_id, is_playable, duration_ms) = get_song_info(track_id) song_name = fix_filename(artists[0]) + ' - ' + fix_filename(name) @@ -170,6 +171,7 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba output_template = output_template.replace("{release_year}", fix_filename(release_year)) output_template = output_template.replace("{disc_number}", fix_filename(disc_number)) output_template = output_template.replace("{track_number}", '{:02d}'.format(int(fix_filename(track_number)))) + output_template = output_template.replace("{total_tracks}", fix_filename(total_tracks)) output_template = output_template.replace("{id}", fix_filename(scraped_song_id)) output_template = output_template.replace("{track_id}", fix_filename(track_id)) output_template = output_template.replace("{ext}", ext) @@ -261,7 +263,7 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba Printer.print(PrintChannel.SKIPS, f"### Skipping lyrics for {song_name}: lyrics not available ###") convert_audio_format(filename_temp) try: - set_audio_tags(filename_temp, artists, genres, 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, total_tracks) set_music_thumbnail(filename_temp, image_url) except Exception: Printer.print(PrintChannel.ERRORS, "Unable to write metadata, ensure ffmpeg is installed and added to your PATH.") diff --git a/zotify/utils.py b/zotify/utils.py index f483b23a..aff2f65f 100644 --- a/zotify/utils.py +++ b/zotify/utils.py @@ -11,7 +11,7 @@ import music_tag import requests -from zotify.const import ARTIST, GENRE, TRACKTITLE, ALBUM, YEAR, DISCNUMBER, TRACKNUMBER, ARTWORK, \ +from zotify.const import ARTIST, GENRE, TRACKTITLE, ALBUM, YEAR, DISCNUMBER, TRACKNUMBER, TOTALTRACKS, ARTWORK, \ WINDOWS_SYSTEM, LINUX_SYSTEM, ALBUMARTIST from zotify.zotify import Zotify @@ -126,7 +126,7 @@ def clear() -> None: os.system('clear') -def set_audio_tags(filename, artists, genres, 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, total_tracks) -> None: """ sets music_tag metadata """ tags = music_tag.load_file(filename) tags[ALBUMARTIST] = artists[0] @@ -137,6 +137,7 @@ def set_audio_tags(filename, artists, genres, name, album_name, release_year, di tags[YEAR] = release_year tags[DISCNUMBER] = disc_number tags[TRACKNUMBER] = track_number + tags[TOTALTRACKS] = total_tracks tags.save() From d301d8fa8d3266ae3ee195030a0f01672a1798cf Mon Sep 17 00:00:00 2001 From: hollis Date: Thu, 4 Jul 2024 13:58:40 +0930 Subject: [PATCH 07/19] move total_tracks under track_number for consistency --- zotify/track.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zotify/track.py b/zotify/track.py index 38e1f919..0ba2c5dc 100644 --- a/zotify/track.py +++ b/zotify/track.py @@ -63,8 +63,8 @@ def get_song_info(song_id) -> Tuple[List[str], List[Any], str, str, Any, Any, An name = info[TRACKS][0][NAME] release_year = info[TRACKS][0][ALBUM][RELEASE_DATE].split('-')[0] disc_number = info[TRACKS][0][DISC_NUMBER] - total_tracks = info[TRACKS][0][ALBUM][TOTAL_TRACKS] track_number = info[TRACKS][0][TRACK_NUMBER] + total_tracks = info[TRACKS][0][ALBUM][TOTAL_TRACKS] scraped_song_id = info[TRACKS][0][ID] is_playable = info[TRACKS][0][IS_PLAYABLE] duration_ms = info[TRACKS][0][DURATION_MS] From 3a89de377d140525bab48ca313ad7e4251dfbd7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Gauvin?= Date: Mon, 27 May 2024 00:56:07 +0200 Subject: [PATCH 08/19] Allow Spotify URL with country code --- zotify/utils.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/zotify/utils.py b/zotify/utils.py index aff2f65f..f25099ba 100644 --- a/zotify/utils.py +++ b/zotify/utils.py @@ -159,42 +159,42 @@ def regex_input_for_urls(search_input) -> Tuple[str, str, str, str, str, str]: track_uri_search = re.search( r'^spotify:track:(?P[0-9a-zA-Z]{22})$', search_input) track_url_search = re.search( - r'^(https?://)?open\.spotify\.com/track/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', + r'^(https?://)?open\.spotify\.com(?:/intl-\w+)?/track/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', search_input, ) album_uri_search = re.search( r'^spotify:album:(?P[0-9a-zA-Z]{22})$', search_input) album_url_search = re.search( - r'^(https?://)?open\.spotify\.com/album/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', + r'^(https?://)?open\.spotify\.com(?:/intl-\w\w)?/album/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', search_input, ) playlist_uri_search = re.search( r'^spotify:playlist:(?P[0-9a-zA-Z]{22})$', search_input) playlist_url_search = re.search( - r'^(https?://)?open\.spotify\.com/playlist/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', + r'^(https?://)?open\.spotify\.com(?:/intl-\w\w)?/playlist/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', search_input, ) episode_uri_search = re.search( r'^spotify:episode:(?P[0-9a-zA-Z]{22})$', search_input) episode_url_search = re.search( - r'^(https?://)?open\.spotify\.com/episode/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', + r'^(https?://)?open\.spotify\.com(?:/intl-\w\w)?/episode/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', search_input, ) show_uri_search = re.search( r'^spotify:show:(?P[0-9a-zA-Z]{22})$', search_input) show_url_search = re.search( - r'^(https?://)?open\.spotify\.com/show/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', + r'^(https?://)?open\.spotify\.com(?:/intl-\w\w)?/show/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', search_input, ) artist_uri_search = re.search( r'^spotify:artist:(?P[0-9a-zA-Z]{22})$', search_input) artist_url_search = re.search( - r'^(https?://)?open\.spotify\.com/artist/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', + r'^(https?://)?open\.spotify\.com(?:/intl-\w\w)?/artist/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', search_input, ) From e1b5d8f6511ea677a5d0ce8171b760ca0b227308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Gauvin?= Date: Mon, 27 May 2024 14:54:41 +0200 Subject: [PATCH 09/19] Allow Spotify URLs with country code --- zotify/utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/zotify/utils.py b/zotify/utils.py index f25099ba..53b24480 100644 --- a/zotify/utils.py +++ b/zotify/utils.py @@ -166,35 +166,35 @@ def regex_input_for_urls(search_input) -> Tuple[str, str, str, str, str, str]: album_uri_search = re.search( r'^spotify:album:(?P[0-9a-zA-Z]{22})$', search_input) album_url_search = re.search( - r'^(https?://)?open\.spotify\.com(?:/intl-\w\w)?/album/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', + r'^(https?://)?open\.spotify\.com(?:/intl-\w+)?/album/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', search_input, ) playlist_uri_search = re.search( r'^spotify:playlist:(?P[0-9a-zA-Z]{22})$', search_input) playlist_url_search = re.search( - r'^(https?://)?open\.spotify\.com(?:/intl-\w\w)?/playlist/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', + r'^(https?://)?open\.spotify\.com(?:/intl-\w+)?/playlist/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', search_input, ) episode_uri_search = re.search( r'^spotify:episode:(?P[0-9a-zA-Z]{22})$', search_input) episode_url_search = re.search( - r'^(https?://)?open\.spotify\.com(?:/intl-\w\w)?/episode/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', + r'^(https?://)?open\.spotify\.com(?:/intl-\w+)?/episode/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', search_input, ) show_uri_search = re.search( r'^spotify:show:(?P[0-9a-zA-Z]{22})$', search_input) show_url_search = re.search( - r'^(https?://)?open\.spotify\.com(?:/intl-\w\w)?/show/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', + r'^(https?://)?open\.spotify\.com(?:/intl-\w+)?/show/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', search_input, ) artist_uri_search = re.search( r'^spotify:artist:(?P[0-9a-zA-Z]{22})$', search_input) artist_url_search = re.search( - r'^(https?://)?open\.spotify\.com(?:/intl-\w\w)?/artist/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', + r'^(https?://)?open\.spotify\.com(?:/intl-\w+)?/artist/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$', search_input, ) From 37d4972edda81997f11d4da3085de983c83605bd Mon Sep 17 00:00:00 2001 From: Haydon Welsh Date: Wed, 10 Apr 2024 10:32:50 +0100 Subject: [PATCH 10/19] Effective libopus support --- zotify/const.py | 2 +- zotify/track.py | 22 ++++++++++++++++------ zotify/utils.py | 1 + 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/zotify/const.py b/zotify/const.py index d50d4006..2327e58c 100644 --- a/zotify/const.py +++ b/zotify/const.py @@ -114,6 +114,6 @@ 'm4a': 'm4a', 'mp3': 'mp3', 'ogg': 'ogg', - 'opus': 'ogg', + 'opus': 'opus', 'vorbis': 'ogg', } diff --git a/zotify/track.py b/zotify/track.py index 0ba2c5dc..baab17f0 100644 --- a/zotify/track.py +++ b/zotify/track.py @@ -306,21 +306,31 @@ def convert_audio_format(filename) -> None: download_format = Zotify.CONFIG.get_download_format().lower() file_codec = CODEC_MAP.get(download_format, 'copy') if file_codec != 'copy': - bitrate = Zotify.CONFIG.get_transcode_bitrate() bitrates = { 'auto': '320k' if Zotify.check_premium() else '160k', 'normal': '96k', 'high': '160k', 'very_high': '320k' } - bitrate = bitrates[Zotify.CONFIG.get_download_quality()] + + # This var does the job of the old 'bitrate' var. + predef_quality_level_bitrate = bitrates[Zotify.CONFIG.get_download_quality()] + # New var to hold user-specified bitrate separately + custom_bitrate = Zotify.CONFIG.get_transcode_bitrate() + + # Check if the user has specified a custom bitrate, ie they've put something in for transcode bitrate, take that and use it as the bitrate. It's more important than the predefined quality levels + if custom_bitrate != None: + set_bitrate = custom_bitrate + else: + set_bitrate = predef_quality_level_bitrate + else: - bitrate = None + set_bitrate = None output_params = ['-c:a', file_codec] - if bitrate: - output_params += ['-b:a', bitrate] - + Printer.print(PrintChannel.DOWNLOADS, f'### Downloading in {file_codec.upper()} ###') + if set_bitrate: + output_params += ['-b:a', set_bitrate] try: ff_m = ffmpy.FFmpeg( global_options=['-y', '-hide_banner', '-loglevel error'], diff --git a/zotify/utils.py b/zotify/utils.py index 53b24480..04cb8ef1 100644 --- a/zotify/utils.py +++ b/zotify/utils.py @@ -19,6 +19,7 @@ class MusicFormat(str, Enum): MP3 = 'mp3', OGG = 'ogg', + OPUS = 'opus' def create_download_directory(download_path: str) -> None: From c040fb2721d4d9097c9d8ff7e42436b0e26c92f0 Mon Sep 17 00:00:00 2001 From: diogosena Date: Fri, 5 Apr 2024 11:51:40 -0300 Subject: [PATCH 11/19] Update track.py Fix _1 filenames, and added m3u8 playlist creation --- zotify/track.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/zotify/track.py b/zotify/track.py index baab17f0..370cd048 100644 --- a/zotify/track.py +++ b/zotify/track.py @@ -189,12 +189,17 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba # a song with the same name is installed if not check_id and check_name: - c = len([file for file in Path(filedir).iterdir() if re.search(f'^{filename}_', str(file))]) + 1 + + if Zotify.CONFIG.get_skip_existing(): + Printer.print(PrintChannel.SKIPS, '\n### SKIPPING: ' + song_name + ' (SONG ALREADY EXISTS) ###' + "\n") + return + else: + c = len([file for file in Path(filedir).iterdir() if re.search(f'^{filename}_', str(file))]) + 1 - fname = PurePath(PurePath(filename).name).parent - ext = PurePath(PurePath(filename).name).suffix + fname = PurePath(filename).stem + ext = PurePath(PurePath(filename).name).suffix - filename = PurePath(filedir).joinpath(f'{fname}_{c}{ext}') + filename = PurePath(filedir).joinpath(f'{fname}_{c}{ext}') except Exception as e: Printer.print(PrintChannel.ERRORS, '### SKIPPING SONG - FAILED TO QUERY METADATA ###') @@ -284,6 +289,22 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba if Zotify.CONFIG.get_bulk_wait_time(): time.sleep(Zotify.CONFIG.get_bulk_wait_time()) + + # Verifica se o nome da playlist foi fornecido + if extra_keys and 'playlist' in extra_keys: + playlist_file = PurePath(Zotify.CONFIG.get_root_path()).joinpath(extra_keys['playlist'] + '.m3u8') + + # Se for o primeiro item da playlist, realiza o truncamento + if extra_keys['playlist_num'].lstrip('0') == '1': + with open(playlist_file, 'w', encoding='utf-8') as f: + f.write("#EXTM3U\n") + + # Adiciona o nome do arquivo da música baixada à playlist M3U8 + with open(playlist_file, "a", encoding='utf-8') as f: + #f.write("#EXTINF:-1," + urlencode(song_name) + "\n") + #f.write(f"{filename}\n") + f.write(f"{filename.relative_to(PurePath(Zotify.CONFIG.get_root_path()))}\n") + except Exception as e: Printer.print(PrintChannel.ERRORS, '### SKIPPING: ' + song_name + ' (GENERAL DOWNLOAD ERROR) ###') Printer.print(PrintChannel.ERRORS, 'Track_ID: ' + str(track_id)) From 20e5e3894938c9eb07d1d4ee6600ac429723c676 Mon Sep 17 00:00:00 2001 From: BridgeSense <88615188+BridgeSenseDev@users.noreply.github.com> Date: Tue, 12 Mar 2024 00:34:58 +0800 Subject: [PATCH 12/19] Update track.py (fix "bad escape") --- zotify/track.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/zotify/track.py b/zotify/track.py index 370cd048..e5edc37b 100644 --- a/zotify/track.py +++ b/zotify/track.py @@ -196,10 +196,17 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba else: c = len([file for file in Path(filedir).iterdir() if re.search(f'^{filename}_', str(file))]) + 1 - fname = PurePath(filename).stem - ext = PurePath(PurePath(filename).name).suffix + + filename_str = str(PurePath(filename)) + escaped_filename = re.escape(filename_str) + pattern = re.compile(f'^{escaped_filename}_') + + c = len([file for file in Path(filedir).iterdir() if pattern.search(str(file))]) + 1 + + fname = PurePath(filename).stem + ext = PurePath(filename).suffix - filename = PurePath(filedir).joinpath(f'{fname}_{c}{ext}') + filename = PurePath(filedir).joinpath(f'{fname}_{c}{ext}') except Exception as e: Printer.print(PrintChannel.ERRORS, '### SKIPPING SONG - FAILED TO QUERY METADATA ###') From bf740da9e12aef426ba4a860661914914197e2d3 Mon Sep 17 00:00:00 2001 From: D3viantS4ge Date: Mon, 22 Jan 2024 23:00:01 -0500 Subject: [PATCH 13/19] Add {artists} template --- zotify/track.py | 1 + 1 file changed, 1 insertion(+) diff --git a/zotify/track.py b/zotify/track.py index e5edc37b..c572d49d 100644 --- a/zotify/track.py +++ b/zotify/track.py @@ -166,6 +166,7 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba ext = EXT_MAP.get(Zotify.CONFIG.get_download_format().lower()) output_template = output_template.replace("{artist}", fix_filename(artists[0])) + output_template = output_template.replace("{artists}", fix_filename(', '.join(artists))) output_template = output_template.replace("{album}", fix_filename(album_name)) output_template = output_template.replace("{song_name}", fix_filename(name)) output_template = output_template.replace("{release_year}", fix_filename(release_year)) From e20939d2bf9a8627ef93f1fca0a4fa387189dd89 Mon Sep 17 00:00:00 2001 From: ryansereno Date: Tue, 28 Nov 2023 09:28:33 -0500 Subject: [PATCH 14/19] compile archive file by default --- zotify/track.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zotify/track.py b/zotify/track.py index c572d49d..75833b47 100644 --- a/zotify/track.py +++ b/zotify/track.py @@ -289,7 +289,7 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba Printer.print(PrintChannel.DOWNLOADS, f'### Downloaded "{song_name}" to "{Path(filename).relative_to(Zotify.CONFIG.get_root_path())}" in {fmt_seconds(time_downloaded - time_start)} (plus {fmt_seconds(time_finished - time_downloaded)} converting) ###' + "\n") # add song id to archive file - if Zotify.CONFIG.get_skip_previously_downloaded(): + if not check_all_time: add_to_archive(scraped_song_id, PurePath(filename).name, artists[0], name) # add song id to download directory's .song_ids file if not check_id: From 1161ebb540a039a8a976309ff2a82d63460800fb Mon Sep 17 00:00:00 2001 From: ryansereno Date: Wed, 13 Dec 2023 21:42:16 -0500 Subject: [PATCH 15/19] sort playlist tracks by date-added --- zotify/playlist.py | 4 +++- zotify/utils.py | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/zotify/playlist.py b/zotify/playlist.py index 919c47ba..655943b9 100644 --- a/zotify/playlist.py +++ b/zotify/playlist.py @@ -1,7 +1,7 @@ from zotify.const import ITEMS, ID, TRACK, NAME from zotify.termoutput import Printer from zotify.track import download_track -from zotify.utils import split_input +from zotify.utils import split_input, strptime_utc from zotify.zotify import Zotify MY_PLAYLISTS_URL = 'https://api.spotify.com/v1/me/playlists' @@ -37,6 +37,8 @@ def get_playlist_songs(playlist_id): if len(resp[ITEMS]) < limit: break + songs.sort(key=lambda s: strptime_utc(s['added_at']), reverse=True) + return songs diff --git a/zotify/utils.py b/zotify/utils.py index 04cb8ef1..6e317b28 100644 --- a/zotify/utils.py +++ b/zotify/utils.py @@ -296,3 +296,7 @@ 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) + +def strptime_utc(dtstr): + return datetime.datetime.strptime(dtstr[:-1], '%Y-%m-%dT%H:%M:%S').replace(tzinfo=datetime.timezone.utc) + From 612039afa2e5f6c5e6b9a50ce2d4eb8f4ad1abc1 Mon Sep 17 00:00:00 2001 From: ryansereno Date: Wed, 13 Dec 2023 21:46:13 -0500 Subject: [PATCH 16/19] reverted --- zotify/track.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zotify/track.py b/zotify/track.py index 75833b47..c572d49d 100644 --- a/zotify/track.py +++ b/zotify/track.py @@ -289,7 +289,7 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba Printer.print(PrintChannel.DOWNLOADS, f'### Downloaded "{song_name}" to "{Path(filename).relative_to(Zotify.CONFIG.get_root_path())}" in {fmt_seconds(time_downloaded - time_start)} (plus {fmt_seconds(time_finished - time_downloaded)} converting) ###' + "\n") # add song id to archive file - if not check_all_time: + if Zotify.CONFIG.get_skip_previously_downloaded(): add_to_archive(scraped_song_id, PurePath(filename).name, artists[0], name) # add song id to download directory's .song_ids file if not check_id: From 146ffb44e5b21265ec242a113661a57fa196f53d Mon Sep 17 00:00:00 2001 From: GitGitro <108683123+GitGitro@users.noreply.github.com> Date: Thu, 1 Aug 2024 15:15:39 +0200 Subject: [PATCH 17/19] translate comments --- zotify/track.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zotify/track.py b/zotify/track.py index c572d49d..263a2bf8 100644 --- a/zotify/track.py +++ b/zotify/track.py @@ -298,16 +298,16 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba if Zotify.CONFIG.get_bulk_wait_time(): time.sleep(Zotify.CONFIG.get_bulk_wait_time()) - # Verifica se o nome da playlist foi fornecido + # Checks if the playlist name was provided if extra_keys and 'playlist' in extra_keys: playlist_file = PurePath(Zotify.CONFIG.get_root_path()).joinpath(extra_keys['playlist'] + '.m3u8') - # Se for o primeiro item da playlist, realiza o truncamento + # If it's the first item in the playlist, it truncates it if extra_keys['playlist_num'].lstrip('0') == '1': with open(playlist_file, 'w', encoding='utf-8') as f: f.write("#EXTM3U\n") - # Adiciona o nome do arquivo da música baixada à playlist M3U8 + # Adds the file name of the downloaded song to the M3U8 playlist with open(playlist_file, "a", encoding='utf-8') as f: #f.write("#EXTINF:-1," + urlencode(song_name) + "\n") #f.write(f"{filename}\n") From 76acc6c44f3681bb030b50ee63ed190aa6c4d8b6 Mon Sep 17 00:00:00 2001 From: GitGitro <108683123+GitGitro@users.noreply.github.com> Date: Thu, 1 Aug 2024 17:34:16 +0200 Subject: [PATCH 18/19] Revert "Effective libopus support" This reverts commit 94407a1b99450f72e28d76a197657e130a588937. --- zotify/const.py | 2 +- zotify/track.py | 22 ++++++---------------- zotify/utils.py | 1 - 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/zotify/const.py b/zotify/const.py index 2327e58c..d50d4006 100644 --- a/zotify/const.py +++ b/zotify/const.py @@ -114,6 +114,6 @@ 'm4a': 'm4a', 'mp3': 'mp3', 'ogg': 'ogg', - 'opus': 'opus', + 'opus': 'ogg', 'vorbis': 'ogg', } diff --git a/zotify/track.py b/zotify/track.py index 263a2bf8..ea5843b7 100644 --- a/zotify/track.py +++ b/zotify/track.py @@ -335,31 +335,21 @@ def convert_audio_format(filename) -> None: download_format = Zotify.CONFIG.get_download_format().lower() file_codec = CODEC_MAP.get(download_format, 'copy') if file_codec != 'copy': + bitrate = Zotify.CONFIG.get_transcode_bitrate() bitrates = { 'auto': '320k' if Zotify.check_premium() else '160k', 'normal': '96k', 'high': '160k', 'very_high': '320k' } - - # This var does the job of the old 'bitrate' var. - predef_quality_level_bitrate = bitrates[Zotify.CONFIG.get_download_quality()] - # New var to hold user-specified bitrate separately - custom_bitrate = Zotify.CONFIG.get_transcode_bitrate() - - # Check if the user has specified a custom bitrate, ie they've put something in for transcode bitrate, take that and use it as the bitrate. It's more important than the predefined quality levels - if custom_bitrate != None: - set_bitrate = custom_bitrate - else: - set_bitrate = predef_quality_level_bitrate - + bitrate = bitrates[Zotify.CONFIG.get_download_quality()] else: - set_bitrate = None + bitrate = None output_params = ['-c:a', file_codec] - Printer.print(PrintChannel.DOWNLOADS, f'### Downloading in {file_codec.upper()} ###') - if set_bitrate: - output_params += ['-b:a', set_bitrate] + if bitrate: + output_params += ['-b:a', bitrate] + try: ff_m = ffmpy.FFmpeg( global_options=['-y', '-hide_banner', '-loglevel error'], diff --git a/zotify/utils.py b/zotify/utils.py index 6e317b28..88bb44d2 100644 --- a/zotify/utils.py +++ b/zotify/utils.py @@ -19,7 +19,6 @@ class MusicFormat(str, Enum): MP3 = 'mp3', OGG = 'ogg', - OPUS = 'opus' def create_download_directory(download_path: str) -> None: From b98e02c038d0d5b33ee9b4c0c5814616ad9e9830 Mon Sep 17 00:00:00 2001 From: diogosena Date: Tue, 16 Apr 2024 10:03:39 -0300 Subject: [PATCH 19/19] Update track.py --- zotify/track.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zotify/track.py b/zotify/track.py index ea5843b7..b11c8000 100644 --- a/zotify/track.py +++ b/zotify/track.py @@ -192,8 +192,8 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba if not check_id and check_name: if Zotify.CONFIG.get_skip_existing(): + prepare_download_loader.stop() Printer.print(PrintChannel.SKIPS, '\n### SKIPPING: ' + song_name + ' (SONG ALREADY EXISTS) ###' + "\n") - return else: c = len([file for file in Path(filedir).iterdir() if re.search(f'^{filename}_', str(file))]) + 1