Files
deezspot-spotizerr-dev/deezspot/spotloader/__init__.py
cool.gitter.not.me.again.duh 089cb3dc5a first commit
2025-05-31 15:51:18 -06:00

527 lines
20 KiB
Python

#!/usr/bin/python3
import traceback
from os.path import isfile
from deezspot.easy_spoty import Spo
from librespot.core import Session
from deezspot.exceptions import InvalidLink
from deezspot.spotloader.__spo_api__ import tracking, tracking_album, tracking_episode
from deezspot.spotloader.spotify_settings import stock_quality
from deezspot.libutils.utils import (
get_ids,
link_is_valid,
what_kind,
)
from deezspot.models import (
Track,
Album,
Playlist,
Preferences,
Smart,
Episode
)
from deezspot.spotloader.__download__ import (
DW_TRACK,
DW_ALBUM,
DW_PLAYLIST,
DW_EPISODE,
Download_JOB,
)
from deezspot.libutils.others_settings import (
stock_output,
stock_recursive_quality,
stock_recursive_download,
stock_not_interface,
stock_zip,
method_save,
is_thread,
stock_real_time_dl
)
from deezspot.libutils.logging_utils import logger, ProgressReporter
class SpoLogin:
def __init__(
self,
credentials_path: str,
spotify_client_id: str = None,
spotify_client_secret: str = None,
progress_callback = None,
silent: bool = False
) -> None:
self.credentials_path = credentials_path
self.spotify_client_id = spotify_client_id
self.spotify_client_secret = spotify_client_secret
# Initialize Spotify API with credentials if provided
if spotify_client_id and spotify_client_secret:
Spo.__init__(client_id=spotify_client_id, client_secret=spotify_client_secret)
logger.info("Initialized Spotify API with provided credentials")
# Configure progress reporting
self.progress_reporter = ProgressReporter(callback=progress_callback, silent=silent)
self.__initialize_session()
def report_progress(self, progress_data):
"""Report progress using the configured reporter."""
self.progress_reporter.report(progress_data)
def __initialize_session(self) -> None:
try:
session_builder = Session.Builder()
session_builder.conf.stored_credentials_file = self.credentials_path
if isfile(self.credentials_path):
session = session_builder.stored_file().create()
logger.info("Successfully initialized Spotify session")
else:
logger.error("Credentials file not found")
raise FileNotFoundError("Please fill your credentials.json location!")
Download_JOB(session)
Download_JOB.set_progress_reporter(self.progress_reporter)
except Exception as e:
logger.error(f"Failed to initialize Spotify session: {str(e)}")
raise
def download_track(
self, link_track,
output_dir=stock_output,
quality_download=stock_quality,
recursive_quality=stock_recursive_quality,
recursive_download=stock_recursive_download,
not_interface=stock_not_interface,
method_save=method_save,
is_thread=is_thread,
real_time_dl=stock_real_time_dl,
custom_dir_format=None,
custom_track_format=None,
pad_tracks=True,
initial_retry_delay=30,
retry_delay_increase=30,
max_retries=5,
convert_to=None
) -> Track:
try:
link_is_valid(link_track)
ids = get_ids(link_track)
song_metadata = tracking(ids)
logger.info(f"Starting download for track: {song_metadata.get('music', 'Unknown')} - {song_metadata.get('artist', 'Unknown')}")
preferences = Preferences()
preferences.real_time_dl = real_time_dl
preferences.link = link_track
preferences.song_metadata = song_metadata
preferences.quality_download = quality_download
preferences.output_dir = output_dir
preferences.ids = ids
preferences.recursive_quality = recursive_quality
preferences.recursive_download = recursive_download
preferences.not_interface = not_interface
preferences.method_save = method_save
preferences.is_episode = False
preferences.custom_dir_format = custom_dir_format
preferences.custom_track_format = custom_track_format
preferences.pad_tracks = pad_tracks
preferences.initial_retry_delay = initial_retry_delay
preferences.retry_delay_increase = retry_delay_increase
preferences.max_retries = max_retries
preferences.convert_to = convert_to
if not is_thread:
track = DW_TRACK(preferences).dw()
else:
track = DW_TRACK(preferences).dw2()
return track
except Exception as e:
logger.error(f"Failed to download track: {str(e)}")
traceback.print_exc()
raise e
def download_album(
self, link_album,
output_dir=stock_output,
quality_download=stock_quality,
recursive_quality=stock_recursive_quality,
recursive_download=stock_recursive_download,
not_interface=stock_not_interface,
make_zip=stock_zip,
method_save=method_save,
is_thread=is_thread,
real_time_dl=stock_real_time_dl,
custom_dir_format=None,
custom_track_format=None,
pad_tracks=True,
initial_retry_delay=30,
retry_delay_increase=30,
max_retries=5,
convert_to=None
) -> Album:
try:
link_is_valid(link_album)
ids = get_ids(link_album)
# Use stored credentials for API calls
album_json = Spo.get_album(ids)
song_metadata = tracking_album(album_json)
logger.info(f"Starting download for album: {song_metadata.get('album', 'Unknown')} - {song_metadata.get('ar_album', 'Unknown')}")
preferences = Preferences()
preferences.real_time_dl = real_time_dl
preferences.link = link_album
preferences.song_metadata = song_metadata
preferences.quality_download = quality_download
preferences.output_dir = output_dir
preferences.ids = ids
preferences.json_data = album_json
preferences.recursive_quality = recursive_quality
preferences.recursive_download = recursive_download
preferences.not_interface = not_interface
preferences.method_save = method_save
preferences.make_zip = make_zip
preferences.is_episode = False
preferences.custom_dir_format = custom_dir_format
preferences.custom_track_format = custom_track_format
preferences.pad_tracks = pad_tracks
preferences.initial_retry_delay = initial_retry_delay
preferences.retry_delay_increase = retry_delay_increase
preferences.max_retries = max_retries
preferences.convert_to = convert_to
if not is_thread:
album = DW_ALBUM(preferences).dw()
else:
album = DW_ALBUM(preferences).dw2()
return album
except Exception as e:
logger.error(f"Failed to download album: {str(e)}")
traceback.print_exc()
raise e
def download_playlist(
self, link_playlist,
output_dir=stock_output,
quality_download=stock_quality,
recursive_quality=stock_recursive_quality,
recursive_download=stock_recursive_download,
not_interface=stock_not_interface,
make_zip=stock_zip,
method_save=method_save,
is_thread=is_thread,
real_time_dl=stock_real_time_dl,
custom_dir_format=None,
custom_track_format=None,
pad_tracks=True,
initial_retry_delay=30,
retry_delay_increase=30,
max_retries=5,
convert_to=None
) -> Playlist:
try:
link_is_valid(link_playlist)
ids = get_ids(link_playlist)
song_metadata = []
# Use stored credentials for API calls
playlist_json = Spo.get_playlist(ids)
logger.info(f"Starting download for playlist: {playlist_json.get('name', 'Unknown')}")
for track in playlist_json['tracks']['items']:
is_track = track['track']
if not is_track:
continue
external_urls = is_track['external_urls']
if not external_urls:
c_song_metadata = f"The track \"{is_track['name']}\" is not available on Spotify :("
logger.warning(f"Track not available: {is_track['name']}")
else:
ids = get_ids(external_urls['spotify'])
c_song_metadata = tracking(ids)
song_metadata.append(c_song_metadata)
preferences = Preferences()
preferences.real_time_dl = real_time_dl
preferences.link = link_playlist
preferences.song_metadata = song_metadata
preferences.quality_download = quality_download
preferences.output_dir = output_dir
preferences.ids = ids
preferences.json_data = playlist_json
preferences.recursive_quality = recursive_quality
preferences.recursive_download = recursive_download
preferences.not_interface = not_interface
preferences.method_save = method_save
preferences.make_zip = make_zip
preferences.is_episode = False
preferences.custom_dir_format = custom_dir_format
preferences.custom_track_format = custom_track_format
preferences.pad_tracks = pad_tracks
preferences.initial_retry_delay = initial_retry_delay
preferences.retry_delay_increase = retry_delay_increase
preferences.max_retries = max_retries
preferences.convert_to = convert_to
if not is_thread:
playlist = DW_PLAYLIST(preferences).dw()
else:
playlist = DW_PLAYLIST(preferences).dw2()
return playlist
except Exception as e:
logger.error(f"Failed to download playlist: {str(e)}")
traceback.print_exc()
raise e
def download_episode(
self, link_episode,
output_dir=stock_output,
quality_download=stock_quality,
recursive_quality=stock_recursive_quality,
recursive_download=stock_recursive_download,
not_interface=stock_not_interface,
method_save=method_save,
is_thread=is_thread,
real_time_dl=stock_real_time_dl,
custom_dir_format=None,
custom_track_format=None,
pad_tracks=True,
initial_retry_delay=30,
retry_delay_increase=30,
max_retries=5,
convert_to=None
) -> Episode:
try:
link_is_valid(link_episode)
ids = get_ids(link_episode)
# Use stored credentials for API calls
episode_json = Spo.get_episode(ids)
episode_metadata = tracking_episode(ids)
logger.info(f"Starting download for episode: {episode_metadata.get('name', 'Unknown')} - {episode_metadata.get('show', 'Unknown')}")
preferences = Preferences()
preferences.real_time_dl = real_time_dl
preferences.link = link_episode
preferences.song_metadata = episode_metadata
preferences.output_dir = output_dir
preferences.ids = ids
preferences.json_data = episode_json
preferences.recursive_quality = recursive_quality
preferences.recursive_download = recursive_download
preferences.not_interface = not_interface
preferences.method_save = method_save
preferences.is_episode = True
preferences.custom_dir_format = custom_dir_format
preferences.custom_track_format = custom_track_format
preferences.pad_tracks = pad_tracks
preferences.initial_retry_delay = initial_retry_delay
preferences.retry_delay_increase = retry_delay_increase
preferences.max_retries = max_retries
preferences.convert_to = convert_to
if not is_thread:
episode = DW_EPISODE(preferences).dw()
else:
episode = DW_EPISODE(preferences).dw2()
return episode
except Exception as e:
logger.error(f"Failed to download episode: {str(e)}")
traceback.print_exc()
raise e
def download_artist(
self, link_artist,
album_type: str = 'album,single,compilation,appears_on',
limit: int = 50,
output_dir=stock_output,
quality_download=stock_quality,
recursive_quality=stock_recursive_quality,
recursive_download=stock_recursive_download,
not_interface=stock_not_interface,
make_zip=stock_zip,
method_save=method_save,
is_thread=is_thread,
real_time_dl=stock_real_time_dl,
custom_dir_format=None,
custom_track_format=None,
pad_tracks=True,
initial_retry_delay=30,
retry_delay_increase=30,
max_retries=5,
convert_to=None
):
"""
Download all albums (or a subset based on album_type and limit) from an artist.
"""
try:
link_is_valid(link_artist)
ids = get_ids(link_artist)
discography = Spo.get_artist(ids, album_type=album_type, limit=limit)
albums = discography.get('items', [])
if not albums:
logger.warning("No albums found for the provided artist")
raise Exception("No albums found for the provided artist.")
logger.info(f"Starting download for artist discography: {discography.get('name', 'Unknown')}")
downloaded_albums = []
for album in albums:
album_url = album.get('external_urls', {}).get('spotify')
if not album_url:
logger.warning(f"No URL found for album: {album.get('name', 'Unknown')}")
continue
downloaded_album = self.download_album(
album_url,
output_dir=output_dir,
quality_download=quality_download,
recursive_quality=recursive_quality,
recursive_download=recursive_download,
not_interface=not_interface,
make_zip=make_zip,
method_save=method_save,
is_thread=is_thread,
real_time_dl=real_time_dl,
custom_dir_format=custom_dir_format,
custom_track_format=custom_track_format,
pad_tracks=pad_tracks,
initial_retry_delay=initial_retry_delay,
retry_delay_increase=retry_delay_increase,
max_retries=max_retries,
convert_to=convert_to
)
downloaded_albums.append(downloaded_album)
return downloaded_albums
except Exception as e:
logger.error(f"Failed to download artist discography: {str(e)}")
traceback.print_exc()
raise e
def download_smart(
self, link,
output_dir=stock_output,
quality_download=stock_quality,
recursive_quality=stock_recursive_quality,
recursive_download=stock_recursive_download,
not_interface=stock_not_interface,
make_zip=stock_zip,
method_save=method_save,
real_time_dl=stock_real_time_dl,
custom_dir_format=None,
custom_track_format=None,
pad_tracks=True,
initial_retry_delay=30,
retry_delay_increase=30,
max_retries=5,
convert_to=None
) -> Smart:
try:
link_is_valid(link)
link = what_kind(link)
smart = Smart()
if "spotify.com" in link:
source = "https://spotify.com"
smart.source = source
logger.info(f"Starting smart download for: {link}")
if "track/" in link:
if not "spotify.com" in link:
raise InvalidLink(link)
track = self.download_track(
link,
output_dir=output_dir,
quality_download=quality_download,
recursive_quality=recursive_quality,
recursive_download=recursive_download,
not_interface=not_interface,
method_save=method_save,
real_time_dl=real_time_dl,
custom_dir_format=custom_dir_format,
custom_track_format=custom_track_format,
pad_tracks=pad_tracks,
initial_retry_delay=initial_retry_delay,
retry_delay_increase=retry_delay_increase,
max_retries=max_retries
)
smart.type = "track"
smart.track = track
elif "album/" in link:
if not "spotify.com" in link:
raise InvalidLink(link)
album = self.download_album(
link,
output_dir=output_dir,
quality_download=quality_download,
recursive_quality=recursive_quality,
recursive_download=recursive_download,
not_interface=not_interface,
make_zip=make_zip,
method_save=method_save,
real_time_dl=real_time_dl,
custom_dir_format=custom_dir_format,
custom_track_format=custom_track_format,
pad_tracks=pad_tracks,
initial_retry_delay=initial_retry_delay,
retry_delay_increase=retry_delay_increase,
max_retries=max_retries
)
smart.type = "album"
smart.album = album
elif "playlist/" in link:
if not "spotify.com" in link:
raise InvalidLink(link)
playlist = self.download_playlist(
link,
output_dir=output_dir,
quality_download=quality_download,
recursive_quality=recursive_quality,
recursive_download=recursive_download,
not_interface=not_interface,
make_zip=make_zip,
method_save=method_save,
real_time_dl=real_time_dl,
custom_dir_format=custom_dir_format,
custom_track_format=custom_track_format,
pad_tracks=pad_tracks,
initial_retry_delay=initial_retry_delay,
retry_delay_increase=retry_delay_increase,
max_retries=max_retries
)
smart.type = "playlist"
smart.playlist = playlist
elif "episode/" in link:
if not "spotify.com" in link:
raise InvalidLink(link)
episode = self.download_episode(
link,
output_dir=output_dir,
quality_download=quality_download,
recursive_quality=recursive_quality,
recursive_download=recursive_download,
not_interface=not_interface,
method_save=method_save,
real_time_dl=real_time_dl,
custom_dir_format=custom_dir_format,
custom_track_format=custom_track_format,
pad_tracks=pad_tracks,
initial_retry_delay=initial_retry_delay,
retry_delay_increase=retry_delay_increase,
max_retries=max_retries
)
smart.type = "episode"
smart.episode = episode
return smart
except Exception as e:
logger.error(f"Failed to perform smart download: {str(e)}")
traceback.print_exc()
raise e