Push V0.2.3
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,5 @@
|
|||||||
from zotify.const import ITEMS, ARTISTS, NAME, ID
|
from zotify.const import ITEMS, ARTISTS, NAME, ID
|
||||||
from zotify.termoutput import Printer
|
from zotify.termoutput import Printer, PrintChannel
|
||||||
from zotify.track import download_track
|
from zotify.track import download_track
|
||||||
from zotify.utils import fix_filename
|
from zotify.utils import fix_filename
|
||||||
from zotify.zotify import Zotify
|
from zotify.zotify import Zotify
|
||||||
@@ -71,9 +71,13 @@ def download_album(album):
|
|||||||
}
|
}
|
||||||
album_multi_disc = len(disc_numbers) > 1
|
album_multi_disc = len(disc_numbers) > 1
|
||||||
|
|
||||||
|
downloaded = 0
|
||||||
|
skipped = 0
|
||||||
|
errored = 0
|
||||||
|
|
||||||
for n, track in Printer.progress(enumerate(tracks, start=1), unit_scale=True, unit='Song', total=len(tracks)):
|
for n, track in Printer.progress(enumerate(tracks, start=1), unit_scale=True, unit='Song', total=len(tracks)):
|
||||||
# Only pass dynamic numbering and album_id (useful for custom templates using {album_id}).
|
# Only pass dynamic numbering and album_id (useful for custom templates using {album_id}).
|
||||||
download_track(
|
result = download_track(
|
||||||
'album',
|
'album',
|
||||||
track[ID],
|
track[ID],
|
||||||
extra_keys={
|
extra_keys={
|
||||||
@@ -85,6 +89,19 @@ def download_album(album):
|
|||||||
disable_progressbar=True
|
disable_progressbar=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if result == 'downloaded':
|
||||||
|
downloaded += 1
|
||||||
|
elif result == 'skipped':
|
||||||
|
skipped += 1
|
||||||
|
else:
|
||||||
|
errored += 1
|
||||||
|
|
||||||
|
total = len(tracks)
|
||||||
|
Printer.print(
|
||||||
|
PrintChannel.PROGRESS_INFO,
|
||||||
|
f'\n#######################################\nFinished! Here is your album summary :\ndownloaded {downloaded}/{total} | skipped {skipped}/{total} | errored {errored}/{total}\n#######################################\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def download_artist_albums(artist):
|
def download_artist_albums(artist):
|
||||||
""" Downloads albums of an artist """
|
""" Downloads albums of an artist """
|
||||||
|
|||||||
@@ -103,8 +103,19 @@ def download_from_urls(urls: list[str]) -> bool:
|
|||||||
download = True
|
download = True
|
||||||
playlist_songs = get_playlist_songs(playlist_id)
|
playlist_songs = get_playlist_songs(playlist_id)
|
||||||
name, _ = get_playlist_info(playlist_id)
|
name, _ = get_playlist_info(playlist_id)
|
||||||
enum = 1
|
track_items = []
|
||||||
char_num = len(str(len(playlist_songs)))
|
for song in playlist_songs:
|
||||||
|
track_obj = song.get(TRACK) if isinstance(song, dict) else None
|
||||||
|
if track_obj and track_obj.get(ID) and track_obj.get(TYPE) != "episode":
|
||||||
|
track_items.append(song)
|
||||||
|
|
||||||
|
expected_total = len(track_items)
|
||||||
|
downloaded_count = 0
|
||||||
|
skipped_count = 0
|
||||||
|
errored_count = 0
|
||||||
|
|
||||||
|
track_enum = 1
|
||||||
|
char_num = len(str(expected_total if expected_total else 1))
|
||||||
for song in playlist_songs:
|
for song in playlist_songs:
|
||||||
track_obj = song.get(TRACK) if isinstance(song, dict) else None
|
track_obj = song.get(TRACK) if isinstance(song, dict) else None
|
||||||
if not track_obj or not track_obj.get(NAME) or not track_obj.get(ID):
|
if not track_obj or not track_obj.get(NAME) or not track_obj.get(ID):
|
||||||
@@ -113,16 +124,27 @@ def download_from_urls(urls: list[str]) -> bool:
|
|||||||
if track_obj.get(TYPE) == "episode": # Playlist item is a podcast episode
|
if track_obj.get(TYPE) == "episode": # Playlist item is a podcast episode
|
||||||
download_episode(track_obj[ID])
|
download_episode(track_obj[ID])
|
||||||
else:
|
else:
|
||||||
download_track('playlist', track_obj[ID], extra_keys=
|
result = download_track('playlist', track_obj[ID], extra_keys=
|
||||||
{
|
{
|
||||||
'playlist_song_name': track_obj[NAME],
|
'playlist_song_name': track_obj[NAME],
|
||||||
'playlist': name,
|
'playlist': name,
|
||||||
'playlist_num': str(enum).zfill(char_num),
|
'playlist_num': str(track_enum).zfill(char_num),
|
||||||
'playlist_total': str(len(playlist_songs)),
|
'playlist_total': str(expected_total),
|
||||||
'playlist_id': playlist_id,
|
'playlist_id': playlist_id,
|
||||||
'playlist_track_id': track_obj[ID]
|
'playlist_track_id': track_obj[ID]
|
||||||
})
|
})
|
||||||
enum += 1
|
track_enum += 1
|
||||||
|
if result == 'downloaded':
|
||||||
|
downloaded_count += 1
|
||||||
|
elif result == 'skipped':
|
||||||
|
skipped_count += 1
|
||||||
|
else:
|
||||||
|
errored_count += 1
|
||||||
|
|
||||||
|
Printer.print(
|
||||||
|
PrintChannel.PROGRESS_INFO,
|
||||||
|
f'\n### PLAYLIST SUMMARY: downloaded {downloaded_count}/{expected_total} | skipped {skipped_count}/{expected_total} | errored {errored_count}/{expected_total} ###\n'
|
||||||
|
)
|
||||||
elif episode_id is not None:
|
elif episode_id is not None:
|
||||||
download = True
|
download = True
|
||||||
download_episode(episode_id)
|
download_episode(episode_id)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import sys
|
|||||||
from pathlib import Path, PurePath
|
from pathlib import Path, PurePath
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
ZOTIFY_VERSION = "0.2.1"
|
ZOTIFY_VERSION = "0.2.2"
|
||||||
ROOT_PATH = 'ROOT_PATH'
|
ROOT_PATH = 'ROOT_PATH'
|
||||||
ROOT_PODCAST_PATH = 'ROOT_PODCAST_PATH'
|
ROOT_PODCAST_PATH = 'ROOT_PODCAST_PATH'
|
||||||
SKIP_EXISTING = 'SKIP_EXISTING'
|
SKIP_EXISTING = 'SKIP_EXISTING'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from zotify.const import ITEMS, ID, TRACK, NAME
|
from zotify.const import ITEMS, ID, TRACK, NAME
|
||||||
from zotify.termoutput import Printer
|
from zotify.termoutput import Printer, PrintChannel
|
||||||
from zotify.track import download_track
|
from zotify.track import download_track
|
||||||
from zotify.utils import split_input
|
from zotify.utils import split_input
|
||||||
from zotify.zotify import Zotify
|
from zotify.zotify import Zotify
|
||||||
@@ -60,24 +60,40 @@ def download_playlist(playlist):
|
|||||||
pl_name, _ = get_playlist_info(playlist[ID])
|
pl_name, _ = get_playlist_info(playlist[ID])
|
||||||
|
|
||||||
playlist_songs = [song for song in get_playlist_songs(playlist[ID]) if song[TRACK] is not None and song[TRACK][ID]]
|
playlist_songs = [song for song in get_playlist_songs(playlist[ID]) if song[TRACK] is not None and song[TRACK][ID]]
|
||||||
p_bar = Printer.progress(playlist_songs, unit='song', total=len(playlist_songs), unit_scale=True)
|
total = len(playlist_songs)
|
||||||
|
downloaded = 0
|
||||||
|
skipped = 0
|
||||||
|
errored = 0
|
||||||
|
|
||||||
|
p_bar = Printer.progress(playlist_songs, unit='song', total=total, unit_scale=True)
|
||||||
enum = 1
|
enum = 1
|
||||||
for song in p_bar:
|
for song in p_bar:
|
||||||
# Use localized playlist name; track metadata (artist/title) is localized in download_track via locale
|
# Use localized playlist name; track metadata (artist/title) is localized in download_track via locale
|
||||||
download_track(
|
result = download_track(
|
||||||
'extplaylist',
|
'extplaylist',
|
||||||
song[TRACK][ID],
|
song[TRACK][ID],
|
||||||
extra_keys={
|
extra_keys={
|
||||||
'playlist': pl_name,
|
'playlist': pl_name,
|
||||||
'playlist_num': str(enum).zfill(2),
|
'playlist_num': str(enum).zfill(2),
|
||||||
'playlist_total': str(len(playlist_songs)),
|
'playlist_total': str(total),
|
||||||
'playlist_id': playlist[ID],
|
'playlist_id': playlist[ID],
|
||||||
},
|
},
|
||||||
disable_progressbar=True
|
disable_progressbar=True
|
||||||
)
|
)
|
||||||
|
if result == 'downloaded':
|
||||||
|
downloaded += 1
|
||||||
|
elif result == 'skipped':
|
||||||
|
skipped += 1
|
||||||
|
else:
|
||||||
|
errored += 1
|
||||||
p_bar.set_description(song[TRACK][NAME])
|
p_bar.set_description(song[TRACK][NAME])
|
||||||
enum += 1
|
enum += 1
|
||||||
|
|
||||||
|
Printer.print(
|
||||||
|
PrintChannel.PROGRESS_INFO,
|
||||||
|
f'\n#######################################\nFinished! Here is your playlist summary :\ndownloaded {downloaded}/{total} | skipped {skipped}/{total} | errored {errored}/{total}\n#######################################\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def download_from_user_playlist():
|
def download_from_user_playlist():
|
||||||
""" Select which playlist(s) to download """
|
""" Select which playlist(s) to download """
|
||||||
|
|||||||
103
zotify/track.py
103
zotify/track.py
@@ -1,25 +1,29 @@
|
|||||||
from pathlib import Path, PurePath
|
from pathlib import Path, PurePath
|
||||||
from typing import Any, Tuple, List, Optional
|
from typing import Any, Tuple, List, Optional, Literal
|
||||||
|
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import ffmpy
|
||||||
from librespot.metadata import TrackId
|
from librespot.metadata import TrackId
|
||||||
|
|
||||||
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, 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, \
|
RELEASE_DATE, ID, TRACKS_URL, FOLLOWED_ARTISTS_URL, SAVED_TRACKS_URL, TRACK_STATS_URL, CODEC_MAP, EXT_MAP, DURATION_MS, \
|
||||||
HREF, ARTISTS, WIDTH
|
HREF, WIDTH
|
||||||
|
from zotify.loader import Loader
|
||||||
from zotify.termoutput import Printer, PrintChannel
|
from zotify.termoutput import Printer, PrintChannel
|
||||||
from zotify.utils import fix_filename, set_audio_tags, set_music_thumbnail, create_download_directory, \
|
from zotify.utils import fix_filename, set_audio_tags, set_music_thumbnail, create_download_directory, \
|
||||||
get_directory_song_ids, add_to_directory_song_ids, get_previously_downloaded, add_to_archive, fmt_seconds
|
get_directory_song_ids, add_to_directory_song_ids, get_previously_downloaded, add_to_archive, fmt_seconds
|
||||||
from zotify.zotify import Zotify
|
from zotify.zotify import Zotify
|
||||||
import traceback
|
|
||||||
from zotify.loader import Loader
|
|
||||||
import math
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
import uuid
|
|
||||||
import json
|
|
||||||
import ffmpy
|
|
||||||
|
|
||||||
# Track whether we've already applied the OGG delay for bulk (album/playlist) downloads
|
|
||||||
_ogg_delay_applied_once = False
|
_ogg_delay_applied_once = False
|
||||||
|
|
||||||
|
DownloadTrackResult = Literal['downloaded', 'skipped', 'errored']
|
||||||
|
|
||||||
def get_saved_tracks() -> list:
|
def get_saved_tracks() -> list:
|
||||||
songs = []
|
songs = []
|
||||||
offset = 0
|
offset = 0
|
||||||
@@ -79,11 +83,9 @@ def ensure_spoticlub_credentials() -> None:
|
|||||||
spoticlub_user = data.get('spoticlub_user') or ''
|
spoticlub_user = data.get('spoticlub_user') or ''
|
||||||
spoticlub_password = data.get('spoticlub_password') or ''
|
spoticlub_password = data.get('spoticlub_password') or ''
|
||||||
|
|
||||||
# If any credential value is missing, prompt the user
|
|
||||||
if not spoticlub_user or not spoticlub_password:
|
if not spoticlub_user or not spoticlub_password:
|
||||||
Printer.print(PrintChannel.PROGRESS_INFO, '\nSpotiClub credentials not found. Please enter them now.')
|
Printer.print(PrintChannel.PROGRESS_INFO, '\nSpotiClub credentials not found. Please enter them now.')
|
||||||
spoticlub_user = input('SpotiClub username: ').strip()
|
spoticlub_user = input('SpotiClub username: ').strip()
|
||||||
# Basic loop to avoid empty submissions
|
|
||||||
while not spoticlub_user:
|
while not spoticlub_user:
|
||||||
spoticlub_user = input('SpotiClub username (cannot be empty): ').strip()
|
spoticlub_user = input('SpotiClub username (cannot be empty): ').strip()
|
||||||
|
|
||||||
@@ -163,7 +165,6 @@ def get_song_genres(rawartists: List[str], track_name: str) -> List[str]:
|
|||||||
elif artist_genres:
|
elif artist_genres:
|
||||||
genres.append(artist_genres[0])
|
genres.append(artist_genres[0])
|
||||||
|
|
||||||
# De-duplicate while preserving order
|
|
||||||
seen = set()
|
seen = set()
|
||||||
genres = [g for g in genres if not (g in seen or seen.add(g))]
|
genres = [g for g in genres if not (g in seen or seen.add(g))]
|
||||||
|
|
||||||
@@ -238,19 +239,18 @@ def get_song_duration(song_id: str) -> float:
|
|||||||
return duration
|
return duration
|
||||||
|
|
||||||
|
|
||||||
def download_track(mode: str, track_id: str, extra_keys=None, disable_progressbar=False) -> None:
|
def download_track(mode: str, track_id: str, extra_keys=None, disable_progressbar=False) -> DownloadTrackResult:
|
||||||
if extra_keys is None:
|
if extra_keys is None:
|
||||||
extra_keys = {}
|
extra_keys = {}
|
||||||
|
|
||||||
# Ensure SpotiClub credentials exist before starting any download prompts or loaders
|
|
||||||
ensure_spoticlub_credentials()
|
ensure_spoticlub_credentials()
|
||||||
|
|
||||||
prepare_download_loader = Loader(PrintChannel.PROGRESS_INFO, "Preparing download...")
|
prepare_download_loader = Loader(PrintChannel.PROGRESS_INFO, "Preparing download...")
|
||||||
prepare_download_loader.start()
|
prepare_download_loader.start()
|
||||||
|
|
||||||
|
try:
|
||||||
try:
|
try:
|
||||||
output_template = str(Zotify.CONFIG.get_output(mode))
|
output_template = str(Zotify.CONFIG.get_output(mode))
|
||||||
|
|
||||||
prepare_download_loader.stop()
|
prepare_download_loader.stop()
|
||||||
|
|
||||||
(artists, raw_artists, album_name, name, image_url, release_year, disc_number,
|
(artists, raw_artists, album_name, name, image_url, release_year, disc_number,
|
||||||
@@ -263,7 +263,6 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba
|
|||||||
|
|
||||||
ext = EXT_MAP.get(Zotify.CONFIG.get_download_format().lower())
|
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("{artist}", fix_filename(artists[0]))
|
||||||
output_template = output_template.replace("{album}", fix_filename(album_name))
|
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("{song_name}", fix_filename(name))
|
||||||
@@ -274,9 +273,6 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba
|
|||||||
output_template = output_template.replace("{track_id}", fix_filename(track_id))
|
output_template = output_template.replace("{track_id}", fix_filename(track_id))
|
||||||
output_template = output_template.replace("{ext}", ext)
|
output_template = output_template.replace("{ext}", ext)
|
||||||
|
|
||||||
# SPLIT_ALBUM_DISCS should only create a Disc folder when the album truly has multiple discs.
|
|
||||||
# - When downloading via zotify.album.download_album(), we pass extra_keys['album_multi_disc'].
|
|
||||||
# - As a fallback, any track with disc_number > 1 implies multi-disc.
|
|
||||||
if mode == 'album' and Zotify.CONFIG.get_split_album_discs():
|
if mode == 'album' and Zotify.CONFIG.get_split_album_discs():
|
||||||
flag_raw = extra_keys.get('album_multi_disc')
|
flag_raw = extra_keys.get('album_multi_disc')
|
||||||
flag = str(flag_raw).strip().lower() in ('1', 'true', 'yes')
|
flag = str(flag_raw).strip().lower() in ('1', 'true', 'yes')
|
||||||
@@ -297,16 +293,17 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba
|
|||||||
|
|
||||||
filename_temp = filename
|
filename_temp = filename
|
||||||
if Zotify.CONFIG.get_temp_download_dir() != '':
|
if Zotify.CONFIG.get_temp_download_dir() != '':
|
||||||
filename_temp = PurePath(Zotify.CONFIG.get_temp_download_dir()).joinpath(f'zotify_{str(uuid.uuid4())}_{track_id}.{ext}')
|
filename_temp = PurePath(Zotify.CONFIG.get_temp_download_dir()).joinpath(
|
||||||
|
f'zotify_{str(uuid.uuid4())}_{track_id}.{ext}'
|
||||||
|
)
|
||||||
|
|
||||||
check_name = Path(filename).is_file() and Path(filename).stat().st_size
|
check_name = Path(filename).is_file() and Path(filename).stat().st_size
|
||||||
check_id = scraped_song_id in get_directory_song_ids(filedir)
|
check_id = scraped_song_id in get_directory_song_ids(filedir)
|
||||||
check_all_time = scraped_song_id in get_previously_downloaded()
|
check_all_time = scraped_song_id in get_previously_downloaded()
|
||||||
|
|
||||||
# a song with the same name is installed
|
|
||||||
if not check_id and check_name:
|
if not check_id and check_name:
|
||||||
stem = PurePath(filename).stem
|
stem = PurePath(filename).stem
|
||||||
ext = PurePath(filename).suffix
|
ext_existing = PurePath(filename).suffix
|
||||||
base_prefix = str(PurePath(filedir).joinpath(stem))
|
base_prefix = str(PurePath(filedir).joinpath(stem))
|
||||||
c = len([
|
c = len([
|
||||||
file
|
file
|
||||||
@@ -314,11 +311,10 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba
|
|||||||
if str(file).startswith(base_prefix + "_")
|
if str(file).startswith(base_prefix + "_")
|
||||||
]) + 1
|
]) + 1
|
||||||
|
|
||||||
# SpotiClub: Fix phantom files when colliding with existing names (-_.mp3)
|
filename = PurePath(filedir).joinpath(f'{stem}_{c}{ext_existing}')
|
||||||
filename = PurePath(filedir).joinpath(f'{stem}_{c}{ext}')
|
|
||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
prepare_download_loader.stop()
|
||||||
Printer.print(PrintChannel.ERRORS, '### SKIPPING SONG - FAILED TO QUERY METADATA ###')
|
Printer.print(PrintChannel.ERRORS, '### SKIPPING SONG - FAILED TO QUERY METADATA ###')
|
||||||
Printer.print(PrintChannel.ERRORS, 'Track_ID: ' + str(track_id))
|
Printer.print(PrintChannel.ERRORS, 'Track_ID: ' + str(track_id))
|
||||||
for k in extra_keys:
|
for k in extra_keys:
|
||||||
@@ -330,12 +326,12 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba
|
|||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
return download_track(mode, track_id, extra_keys)
|
return download_track(mode, track_id, extra_keys)
|
||||||
|
|
||||||
else:
|
|
||||||
try:
|
try:
|
||||||
if not is_playable:
|
if not is_playable:
|
||||||
prepare_download_loader.stop()
|
prepare_download_loader.stop()
|
||||||
Printer.print(PrintChannel.SKIPS, '\n### SKIPPING: ' + song_name + ' (SONG IS UNAVAILABLE) ###' + "\n")
|
Printer.print(PrintChannel.SKIPS, '\n### SKIPPING: ' + song_name + ' (SONG IS UNAVAILABLE) ###' + "\n")
|
||||||
else:
|
return 'skipped'
|
||||||
|
|
||||||
if check_name and Zotify.CONFIG.get_skip_existing():
|
if check_name and Zotify.CONFIG.get_skip_existing():
|
||||||
prepare_download_loader.stop()
|
prepare_download_loader.stop()
|
||||||
Printer.print(PrintChannel.SKIPS, '\n### SKIPPING: ' + song_name + ' (SONG ALREADY EXISTS) ###' + "\n")
|
Printer.print(PrintChannel.SKIPS, '\n### SKIPPING: ' + song_name + ' (SONG ALREADY EXISTS) ###' + "\n")
|
||||||
@@ -354,8 +350,9 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba
|
|||||||
get_song_lyrics(track_id, lrc_path, title=name, artists=artists, album=album_name, duration_ms=duration_ms)
|
get_song_lyrics(track_id, lrc_path, title=name, artists=artists, album=album_name, duration_ms=duration_ms)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
Printer.print(PrintChannel.SKIPS, f"### LYRICS_UNAVAILABLE: Lyrics for {song_name} not available or API returned empty/errored response ###")
|
Printer.print(PrintChannel.SKIPS, f"### LYRICS_UNAVAILABLE: Lyrics for {song_name} not available or API returned empty/errored response ###")
|
||||||
|
return 'skipped'
|
||||||
|
|
||||||
elif check_all_time and Zotify.CONFIG.get_skip_previously_downloaded():
|
if check_all_time and Zotify.CONFIG.get_skip_previously_downloaded():
|
||||||
prepare_download_loader.stop()
|
prepare_download_loader.stop()
|
||||||
Printer.print(PrintChannel.SKIPS, '\n### SKIPPING: ' + song_name + ' (SONG ALREADY DOWNLOADED ONCE) ###' + "\n")
|
Printer.print(PrintChannel.SKIPS, '\n### SKIPPING: ' + song_name + ' (SONG ALREADY DOWNLOADED ONCE) ###' + "\n")
|
||||||
if Zotify.CONFIG.get_always_check_lyrics() and Zotify.CONFIG.get_download_lyrics():
|
if Zotify.CONFIG.get_always_check_lyrics() and Zotify.CONFIG.get_download_lyrics():
|
||||||
@@ -370,15 +367,13 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba
|
|||||||
get_song_lyrics(track_id, lrc_path, title=name, artists=artists, album=album_name, duration_ms=duration_ms)
|
get_song_lyrics(track_id, lrc_path, title=name, artists=artists, album=album_name, duration_ms=duration_ms)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
Printer.print(PrintChannel.SKIPS, f"### LYRICS_UNAVAILABLE: Lyrics for {song_name} not available or API returned empty/errored response ###")
|
Printer.print(PrintChannel.SKIPS, f"### LYRICS_UNAVAILABLE: Lyrics for {song_name} not available or API returned empty/errored response ###")
|
||||||
|
return 'skipped'
|
||||||
|
|
||||||
else:
|
|
||||||
prog_prefix = ''
|
prog_prefix = ''
|
||||||
if mode == 'album':
|
if mode == 'album':
|
||||||
cur = extra_keys.get('album_num')
|
cur = extra_keys.get('album_num')
|
||||||
total = extra_keys.get('album_total')
|
total = extra_keys.get('album_total')
|
||||||
|
|
||||||
if cur and not total:
|
if cur and not total:
|
||||||
# No info about total tracks? Let's query the album from Spotify's API in last resort
|
|
||||||
try:
|
try:
|
||||||
album_id = extra_keys.get('album_id')
|
album_id = extra_keys.get('album_id')
|
||||||
if album_id:
|
if album_id:
|
||||||
@@ -395,15 +390,12 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba
|
|||||||
total = str(total_val)
|
total = str(total_val)
|
||||||
except Exception:
|
except Exception:
|
||||||
total = total
|
total = total
|
||||||
|
|
||||||
if cur and total:
|
if cur and total:
|
||||||
prog_prefix = f'({cur}/{total}) '
|
prog_prefix = f'({cur}/{total}) '
|
||||||
elif mode in ('playlist', 'extplaylist'):
|
elif mode in ('playlist', 'extplaylist'):
|
||||||
cur = extra_keys.get('playlist_num')
|
cur = extra_keys.get('playlist_num')
|
||||||
total = extra_keys.get('playlist_total')
|
total = extra_keys.get('playlist_total')
|
||||||
|
|
||||||
if cur and not total:
|
if cur and not total:
|
||||||
# Same fallback for total tracks in playlist
|
|
||||||
try:
|
try:
|
||||||
playlist_id = extra_keys.get('playlist_id')
|
playlist_id = extra_keys.get('playlist_id')
|
||||||
if playlist_id:
|
if playlist_id:
|
||||||
@@ -420,7 +412,6 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba
|
|||||||
total = str(total_val)
|
total = str(total_val)
|
||||||
except Exception:
|
except Exception:
|
||||||
total = total
|
total = total
|
||||||
|
|
||||||
if cur and total:
|
if cur and total:
|
||||||
prog_prefix = f'({cur}/{total}) '
|
prog_prefix = f'({cur}/{total}) '
|
||||||
|
|
||||||
@@ -428,20 +419,18 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba
|
|||||||
PrintChannel.PROGRESS_INFO,
|
PrintChannel.PROGRESS_INFO,
|
||||||
f'\n### {prog_prefix}STARTING "{song_name}" ###\n'
|
f'\n### {prog_prefix}STARTING "{song_name}" ###\n'
|
||||||
)
|
)
|
||||||
|
|
||||||
if ext == 'ogg':
|
if ext == 'ogg':
|
||||||
# SpotiClub : TEMP? : For albums/playlists, wait 5 seconds between OGG tracks to avoid
|
|
||||||
# spamming the SpotiClub API for audio keys.
|
|
||||||
# Skip the very first track in the run and for single-track downloads.
|
|
||||||
global _ogg_delay_applied_once
|
global _ogg_delay_applied_once
|
||||||
if mode in ('album', 'playlist', 'extplaylist'):
|
if mode in ('album', 'playlist', 'extplaylist'):
|
||||||
if _ogg_delay_applied_once:
|
if _ogg_delay_applied_once:
|
||||||
# Spammy log
|
|
||||||
# Printer.print(PrintChannel.PROGRESS_INFO, '\n## OGG File : Waiting 5 seconds before resuming... ##')
|
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
else:
|
else:
|
||||||
_ogg_delay_applied_once = True
|
_ogg_delay_applied_once = True
|
||||||
|
|
||||||
if track_id != scraped_song_id:
|
if track_id != scraped_song_id:
|
||||||
track_id = scraped_song_id
|
track_id = scraped_song_id
|
||||||
|
|
||||||
track = TrackId.from_base62(track_id)
|
track = TrackId.from_base62(track_id)
|
||||||
stream = Zotify.get_content_stream(track, Zotify.DOWNLOAD_QUALITY)
|
stream = Zotify.get_content_stream(track, Zotify.DOWNLOAD_QUALITY)
|
||||||
create_download_directory(filedir)
|
create_download_directory(filedir)
|
||||||
@@ -493,10 +482,20 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba
|
|||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
Printer.print(PrintChannel.SKIPS, f"### LYRICS_UNAVAILABLE: Lyrics for {song_name} not available or API returned empty/errored response ###")
|
Printer.print(PrintChannel.SKIPS, f"### LYRICS_UNAVAILABLE: Lyrics for {song_name} not available or API returned empty/errored response ###")
|
||||||
|
|
||||||
convert_audio_format(filename_temp)
|
convert_audio_format(filename_temp)
|
||||||
try:
|
try:
|
||||||
set_audio_tags(filename_temp, artists, genres, name, album_name, release_year, disc_number, track_number,
|
set_audio_tags(
|
||||||
lyrics_lines)
|
filename_temp,
|
||||||
|
artists,
|
||||||
|
genres,
|
||||||
|
name,
|
||||||
|
album_name,
|
||||||
|
release_year,
|
||||||
|
disc_number,
|
||||||
|
track_number,
|
||||||
|
lyrics_lines
|
||||||
|
)
|
||||||
set_music_thumbnail(filename_temp, image_url)
|
set_music_thumbnail(filename_temp, image_url)
|
||||||
except Exception:
|
except Exception:
|
||||||
Printer.print(PrintChannel.ERRORS, "Unable to write metadata, ensure ffmpeg is installed and added to your PATH.")
|
Printer.print(PrintChannel.ERRORS, "Unable to write metadata, ensure ffmpeg is installed and added to your PATH.")
|
||||||
@@ -506,7 +505,11 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba
|
|||||||
|
|
||||||
time_finished = time.time()
|
time_finished = time.time()
|
||||||
|
|
||||||
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")
|
Printer.print(
|
||||||
|
PrintChannel.DOWNLOADS,
|
||||||
|
f'### Downloaded "{song_name}" to "{Path(filename).relative_to(Zotify.CONFIG.get_root_path())}" '
|
||||||
|
f'in {fmt_seconds(time_downloaded - time_start)} (plus {fmt_seconds(time_finished - time_downloaded)} converting) ###\n'
|
||||||
|
)
|
||||||
|
|
||||||
if Zotify.CONFIG.get_skip_previously_downloaded():
|
if Zotify.CONFIG.get_skip_previously_downloaded():
|
||||||
add_to_archive(scraped_song_id, PurePath(filename).name, artists[0], name)
|
add_to_archive(scraped_song_id, PurePath(filename).name, artists[0], name)
|
||||||
@@ -515,7 +518,11 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba
|
|||||||
|
|
||||||
if Zotify.CONFIG.get_bulk_wait_time():
|
if Zotify.CONFIG.get_bulk_wait_time():
|
||||||
time.sleep(Zotify.CONFIG.get_bulk_wait_time())
|
time.sleep(Zotify.CONFIG.get_bulk_wait_time())
|
||||||
|
|
||||||
|
return 'downloaded'
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
prepare_download_loader.stop()
|
||||||
Printer.print(PrintChannel.ERRORS, '### SKIPPING: ' + song_name + ' (GENERAL DOWNLOAD ERROR) ###')
|
Printer.print(PrintChannel.ERRORS, '### SKIPPING: ' + song_name + ' (GENERAL DOWNLOAD ERROR) ###')
|
||||||
Printer.print(PrintChannel.ERRORS, 'Track_ID: ' + str(track_id))
|
Printer.print(PrintChannel.ERRORS, 'Track_ID: ' + str(track_id))
|
||||||
for k in extra_keys:
|
for k in extra_keys:
|
||||||
@@ -523,9 +530,11 @@ def download_track(mode: str, track_id: str, extra_keys=None, disable_progressba
|
|||||||
Printer.print(PrintChannel.ERRORS, "\n")
|
Printer.print(PrintChannel.ERRORS, "\n")
|
||||||
Printer.print(PrintChannel.ERRORS, str(e) + "\n")
|
Printer.print(PrintChannel.ERRORS, str(e) + "\n")
|
||||||
Printer.print(PrintChannel.ERRORS, "".join(traceback.TracebackException.from_exception(e).format()) + "\n")
|
Printer.print(PrintChannel.ERRORS, "".join(traceback.TracebackException.from_exception(e).format()) + "\n")
|
||||||
if Path(filename_temp).exists():
|
if 'filename_temp' in locals() and Path(filename_temp).exists():
|
||||||
Path(filename_temp).unlink()
|
Path(filename_temp).unlink()
|
||||||
|
return 'errored'
|
||||||
|
|
||||||
|
finally:
|
||||||
prepare_download_loader.stop()
|
prepare_download_loader.stop()
|
||||||
|
|
||||||
|
|
||||||
@@ -538,7 +547,7 @@ def convert_audio_format(filename) -> None:
|
|||||||
if file_codec != 'copy':
|
if file_codec != 'copy':
|
||||||
bitrate = Zotify.CONFIG.get_transcode_bitrate()
|
bitrate = Zotify.CONFIG.get_transcode_bitrate()
|
||||||
bitrates = {
|
bitrates = {
|
||||||
#SpotiClub API permit the use of '320k' for free users, so we map 'auto' to that value.
|
#SpotiClub : API permit the use of '320k' for free users, so we map 'auto' to that value.
|
||||||
'auto': '320k',
|
'auto': '320k',
|
||||||
'normal': '96k',
|
'normal': '96k',
|
||||||
'high': '160k',
|
'high': '160k',
|
||||||
|
|||||||
Reference in New Issue
Block a user