From cc947fe374f8ab8329849eb0f97696461cf30153 Mon Sep 17 00:00:00 2001 From: Xoconoch Date: Tue, 10 Jun 2025 18:46:53 -0600 Subject: [PATCH] Implemented callback models --- .gitignore | 2 + deezspot/__init__.py | 2 +- deezspot/__taggers__.py | 2 +- deezspot/deezloader/__download__.py | 2 +- deezspot/deezloader/__init__.py | 2 +- deezspot/libutils/logging_utils.py | 108 +-- deezspot/libutils/utils.py | 2 +- deezspot/models/__init__.py | 8 - deezspot/models/callback/__init__.py | 22 +- deezspot/models/callback/album.py | 34 +- deezspot/models/callback/artist.py | 12 - deezspot/models/callback/callbacks.py | 133 ++++ deezspot/models/callback/playlist.py | 18 +- deezspot/models/callback/track.py | 51 +- deezspot/models/callback/user.py | 14 + deezspot/models/download/__init__.py | 8 + deezspot/models/{ => download}/album.py | 2 +- deezspot/models/{ => download}/episode.py | 0 deezspot/models/{ => download}/playlist.py | 2 +- deezspot/models/{ => download}/preferences.py | 0 deezspot/models/{ => download}/smart.py | 6 +- deezspot/models/{ => download}/track.py | 0 deezspot/spotloader/__download__.py | 635 ++++++++++-------- deezspot/spotloader/__init__.py | 2 +- 24 files changed, 633 insertions(+), 434 deletions(-) delete mode 100644 deezspot/models/__init__.py create mode 100644 deezspot/models/callback/callbacks.py create mode 100644 deezspot/models/callback/user.py create mode 100644 deezspot/models/download/__init__.py rename deezspot/models/{ => download}/album.py (88%) rename deezspot/models/{ => download}/episode.py (100%) rename deezspot/models/{ => download}/playlist.py (71%) rename deezspot/models/{ => download}/preferences.py (100%) rename deezspot/models/{ => download}/smart.py (55%) rename deezspot/models/{ => download}/track.py (100%) diff --git a/.gitignore b/.gitignore index 066ddb7..31d74d1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ deezspot/__pycache__ deezspot/deezloader/__pycache__ deezspot/libutils/__pycache__ Test.py +deezspot/models/callback/__pycache__/ +deezspot/models/download/__pycache__/ diff --git a/deezspot/__init__.py b/deezspot/__init__.py index bb518da..b99f282 100644 --- a/deezspot/__init__.py +++ b/deezspot/__init__.py @@ -9,7 +9,7 @@ from deezspot.libutils.logging_utils import configure_logger, logger # Export key functionality from deezspot.deezloader import DeeLogin -from deezspot.models import Track, Album, Playlist, Smart, Episode +from deezspot.models.download import Track, Album, Playlist, Smart, Episode __version__ = "1.2.0" diff --git a/deezspot/__taggers__.py b/deezspot/__taggers__.py index 297d154..5395910 100644 --- a/deezspot/__taggers__.py +++ b/deezspot/__taggers__.py @@ -11,7 +11,7 @@ from mutagen.id3 import ( APIC, COMM, SYLT, TALB, TCOM, TCON, TCOP, TDRC, TEXT, TIT2, TLEN, TPE1, TPE2, TPOS, TPUB, TRCK, TSRC, TXXX, USLT, TYER ) -from deezspot.models import Track, Episode +from deezspot.models.download import Track, Episode import requests import logging import os diff --git a/deezspot/deezloader/__download__.py b/deezspot/deezloader/__download__.py index 9498adb..623b01e 100644 --- a/deezspot/deezloader/__download__.py +++ b/deezspot/deezloader/__download__.py @@ -17,7 +17,7 @@ from deezspot.exceptions import ( NoRightOnMedia, QualityNotFound, ) -from deezspot.models import ( +from deezspot.models.download import ( Track, Album, Playlist, diff --git a/deezspot/deezloader/__init__.py b/deezspot/deezloader/__init__.py index fdadaff..a88795c 100644 --- a/deezspot/deezloader/__init__.py +++ b/deezspot/deezloader/__init__.py @@ -6,7 +6,7 @@ from deezspot.deezloader.dee_api import API from deezspot.easy_spoty import Spo from deezspot.deezloader.deegw_api import API_GW from deezspot.deezloader.deezer_settings import stock_quality -from deezspot.models import ( +from deezspot.models.download import ( Track, Album, Playlist, diff --git a/deezspot/libutils/logging_utils.py b/deezspot/libutils/logging_utils.py index e0df632..b88b57c 100644 --- a/deezspot/libutils/logging_utils.py +++ b/deezspot/libutils/logging_utils.py @@ -5,6 +5,25 @@ import sys from typing import Optional, Callable, Dict, Any, Union import json +from deezspot.models.callback.callbacks import ( + BaseStatusObject, + initializingObject, + skippedObject, + retryingObject, + realTimeObject, + errorObject, + doneObject, + summaryObject, + failedTrackObject, + trackCallbackObject, + albumCallbackObject, + playlistCallbackObject +) +from deezspot.models.callback.track import trackObject, albumTrackObject, playlistTrackObject, artistTrackObject +from deezspot.models.callback.album import albumObject +from deezspot.models.callback.playlist import playlistObject +from deezspot.models.callback.user import userObject + # Create the main library logger logger = logging.getLogger('deezspot') @@ -88,7 +107,7 @@ class ProgressReporter: # # [ Type: "track" ] # - song: (str) The name of the track. -# - artist: (str) The artist of the track. +# - artists: (str) The artist of the track. # - album: (str, optional) The album of the track. # - parent: (dict, optional) Information about the container (album/playlist). # { "type": "album"|"playlist", "name": str, "owner": str, "artist": str, ... } @@ -132,82 +151,21 @@ class ProgressReporter: def report_progress( reporter: Optional["ProgressReporter"], - report_type: str, - status: str, - song: Optional[str] = None, - artist: Optional[str] = None, - album: Optional[str] = None, - url: Optional[str] = None, - convert_to: Optional[str] = None, - bitrate: Optional[str] = None, - parent: Optional[Dict[str, Any]] = None, - current_track: Optional[int] = None, - total_tracks: Optional[Union[int, str]] = None, - reason: Optional[str] = None, - summary: Optional[Dict[str, Any]] = None, - error: Optional[str] = None, - retry_count: Optional[int] = None, - seconds_left: Optional[int] = None, - time_elapsed: Optional[int] = None, - progress: Optional[int] = None, - owner: Optional[str] = None, - name: Optional[str] = None, - title: Optional[str] = None, + callback_obj: Union[trackCallbackObject, albumCallbackObject, playlistCallbackObject] ): - """Builds and reports a standardized progress dictionary after validating the input.""" + """ + Reports progress using a standardized callback object. - # --- Input Validation --- - # Enforce the standardized format to ensure consistent reporting. - if report_type == "track": - if not all([song, artist]): - raise ValueError("For report_type 'track', 'song' and 'artist' parameters are required.") - if status == "skipped" and reason is None: - raise ValueError("For a 'skipped' track, a 'reason' is required.") - if status == "retrying" and not all(p is not None for p in [retry_count, seconds_left, error]): - raise ValueError("For a 'retrying' track, 'retry_count', 'seconds_left', and 'error' are required.") - if status == "real-time" and not all(p is not None for p in [time_elapsed, progress]): - raise ValueError("For a 'real-time' track, 'time_elapsed' and 'progress' are required.") - if status == "error" and error is None: - raise ValueError("For an 'error' track, an 'error' message is required.") - - elif report_type == "album": - if not all(p is not None for p in [title, artist, total_tracks]): - raise ValueError("For report_type 'album', 'title', 'artist', and 'total_tracks' are required.") - if status == "done" and summary is None: - raise ValueError("For an 'album' with status 'done', a 'summary' is required.") - - elif report_type == "playlist": - if not all(p is not None for p in [name, owner, total_tracks]): - raise ValueError("For report_type 'playlist', 'name', 'owner', and 'total_tracks' are required.") - if status == "done" and summary is None: - raise ValueError("For a 'playlist' with status 'done', a 'summary' is required.") - - elif report_type == "episode": - if not all([song, artist]): # song=episode_title, artist=show_name - raise ValueError("For report_type 'episode', 'song' and 'artist' parameters are required.") - if status == "retrying" and not all(p is not None for p in [retry_count, seconds_left, error]): - raise ValueError("For a 'retrying' episode, 'retry_count', 'seconds_left', and 'error' are required.") - if status == "error" and error is None: - raise ValueError("For an 'error' episode, an 'error' message is required.") - - # --- Report Building --- - report = {"type": report_type, "status": status} + Args: + reporter: The ProgressReporter to use for reporting + callback_obj: A callback object of type trackCallbackObject, albumCallbackObject, or playlistCallbackObject + """ + # Validate the callback object type + if not isinstance(callback_obj, (trackCallbackObject, albumCallbackObject, playlistCallbackObject)): + raise TypeError(f"callback_obj must be of type trackCallbackObject, albumCallbackObject, or playlistCallbackObject, got {type(callback_obj)}") - data_fields = { - "song": song, "artist": artist, "album": album, "url": url, - "convert_to": convert_to, "bitrate": bitrate, "parent": parent, - "current_track": current_track, "total_tracks": total_tracks, - "reason": reason, "summary": summary, "error": error, - "retry_count": retry_count, "seconds_left": seconds_left, - "time_elapsed": time_elapsed, "progress": progress, - "owner": owner, "name": name, "title": title - } - - for key, value in data_fields.items(): - if value is not None: - report[key] = value - + # Convert the callback object to a dictionary and report it if reporter: - reporter.report(report) + reporter.report(callback_obj.__dict__) else: - logger.info(json.dumps(report)) \ No newline at end of file + logger.info(json.dumps(callback_obj.__dict__)) \ No newline at end of file diff --git a/deezspot/libutils/utils.py b/deezspot/libutils/utils.py index 20019cf..3c04f3f 100644 --- a/deezspot/libutils/utils.py +++ b/deezspot/libutils/utils.py @@ -6,7 +6,7 @@ from datetime import datetime from urllib.parse import urlparse from requests import get as req_get from zipfile import ZipFile, ZIP_DEFLATED -from deezspot.models.track import Track +from deezspot.models.download.track import Track from deezspot.exceptions import InvalidLink from deezspot.libutils.others_settings import supported_link, header from deezspot.libutils.logging_utils import ProgressReporter, logger diff --git a/deezspot/models/__init__.py b/deezspot/models/__init__.py deleted file mode 100644 index 2847ff6..0000000 --- a/deezspot/models/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/python3 - -from deezspot.models.smart import Smart -from deezspot.models.track import Track -from deezspot.models.album import Album -from deezspot.models.playlist import Playlist -from deezspot.models.preferences import Preferences -from deezspot.models.episode import Episode diff --git a/deezspot/models/callback/__init__.py b/deezspot/models/callback/__init__.py index f184df0..ccaf979 100644 --- a/deezspot/models/callback/__init__.py +++ b/deezspot/models/callback/__init__.py @@ -5,7 +5,21 @@ Callback data models for the music metadata schema. """ from .common import IDs, ReleaseDate -from .artist import artistObject, artistTrackObject -from .album import albumObject, albumTrackObject, albumArtistObject, trackAlbumObject -from .track import trackObject -from .playlist import playlistObject, trackPlaylistObject, albumTrackPlaylistObject, artistTrackPlaylistObject \ No newline at end of file +from .artist import artistObject, albumArtistObject +from .album import albumObject, trackAlbumObject +from .track import trackObject, artistTrackObject, albumTrackObject, playlistTrackObject +from .playlist import playlistObject, trackPlaylistObject, albumTrackPlaylistObject, artistTrackPlaylistObject +from .callbacks import ( + BaseStatusObject, + initializingObject, + skippedObject, + retryingObject, + realTimeObject, + errorObject, + doneObject, + summaryObject, + failedTrackObject, + trackCallbackObject, + albumCallbackObject, + playlistCallbackObject +) \ No newline at end of file diff --git a/deezspot/models/callback/album.py b/deezspot/models/callback/album.py index b4ab008..617f760 100644 --- a/deezspot/models/callback/album.py +++ b/deezspot/models/callback/album.py @@ -5,7 +5,22 @@ from typing import List, Optional, Dict, Any from .common import IDs, ReleaseDate +@dataclass +class artistTrackAlbumObject: + """Artist representation for a track in an album context.""" + type: str = "artistTrackAlbum" + name: str = "" + ids: IDs = field(default_factory=IDs) + +@dataclass +class artistAlbumObject: + """Artist representation for an album.""" + type: str = "artistAlbum" + name: str = "" + genres: List[str] = field(default_factory=list) + ids: IDs = field(default_factory=IDs) + @dataclass class trackAlbumObject: """Track when nested inside an album context.""" @@ -16,18 +31,7 @@ class trackAlbumObject: duration_ms: int = 0 genres: List[str] = field(default_factory=list) ids: IDs = field(default_factory=IDs) - - -@dataclass -class albumTrackObject: - """Album when nested inside a track context.""" - type: str = "albumTrack" - album_type: str = "" # "album" | "single" | "compilation" - title: str = "" - release_date: Dict[str, Any] = field(default_factory=dict) # ReleaseDate as dict - total_tracks: int = 0 - genres: List[str] = field(default_factory=list) - ids: IDs = field(default_factory=IDs) + artists: List[artistTrackAlbumObject] = field(default_factory=list) @dataclass @@ -36,9 +40,9 @@ class albumObject: type: str = "album" album_type: str = "" # "album" | "single" | "compilation" title: str = "" - release_date: Dict[str, Any] = field(default_factory=dict) # ReleaseDate as dict + release_date: Dict[str, Any] = field(default_factory=dict) total_tracks: int = 0 genres: List[str] = field(default_factory=list) ids: IDs = field(default_factory=IDs) - # Nested: album's tracks without redundant album info - tracks: List[trackAlbumObject] = field(default_factory=list) \ No newline at end of file + tracks: List[trackAlbumObject] = field(default_factory=list) + artists: List[artistAlbumObject] = field(default_factory=list) \ No newline at end of file diff --git a/deezspot/models/callback/artist.py b/deezspot/models/callback/artist.py index 37b29ad..691693f 100644 --- a/deezspot/models/callback/artist.py +++ b/deezspot/models/callback/artist.py @@ -6,17 +6,6 @@ from typing import List, Optional from .common import IDs -@dataclass -class artistTrackObject: - """ - An artist when nested inside a track context. - No genres, no albums—just identifying info. - """ - type: str = "artistTrack" - name: str = "" - ids: IDs = field(default_factory=IDs) - - @dataclass class albumArtistObject: """Album when nested inside an artist context.""" @@ -35,5 +24,4 @@ class artistObject: name: str = "" genres: List[str] = field(default_factory=list) ids: IDs = field(default_factory=IDs) - # Nested: artist's albums without redundant artist info albums: List[albumArtistObject] = field(default_factory=list) \ No newline at end of file diff --git a/deezspot/models/callback/callbacks.py b/deezspot/models/callback/callbacks.py new file mode 100644 index 0000000..241879c --- /dev/null +++ b/deezspot/models/callback/callbacks.py @@ -0,0 +1,133 @@ +#!/usr/bin/python3 + +from dataclasses import dataclass, field +from typing import List, Optional, Dict, Any, Union + +from .common import IDs +from .track import trackObject, albumTrackObject, playlistTrackObject +from .album import albumObject +from .playlist import playlistObject + + +@dataclass +class BaseStatusObject: + """Base class for all status objects with common fields.""" + ids: Optional[IDs] = None + convert_to: Optional[str] = None + bitrate: Optional[str] = None + + +@dataclass +class initializingObject(BaseStatusObject): + """Status object for 'initializing' state.""" + status: str = "initializing" + + +@dataclass +class skippedObject(BaseStatusObject): + """Status object for 'skipped' state.""" + status: str = "skipped" + reason: str = "" + + +@dataclass +class retryingObject(BaseStatusObject): + """Status object for 'retrying' state.""" + status: str = "retrying" + retry_count: int = 0 + seconds_left: int = 0 + error: str = "" + + +@dataclass +class realTimeObject(BaseStatusObject): + """Status object for 'real-time' state.""" + status: str = "real-time" + time_elapsed: int = 0 + progress: int = 0 + + +@dataclass +class errorObject(BaseStatusObject): + """Status object for 'error' state.""" + status: str = "error" + error: str = "" + + +@dataclass +class failedTrackObject: + """Represents a failed track with a reason.""" + track: trackObject = field(default_factory=trackObject) + reason: str = "" + + +@dataclass +class summaryObject: + """Summary of a download operation for an album or playlist.""" + successful_tracks: List[trackObject] = field(default_factory=list) + skipped_tracks: List[trackObject] = field(default_factory=list) + failed_tracks: List[failedTrackObject] = field(default_factory=list) + total_successful: int = 0 + total_skipped: int = 0 + total_failed: int = 0 + + +@dataclass +class doneObject(BaseStatusObject): + """Status object for 'done' state.""" + status: str = "done" + summary: Optional[summaryObject] = None + + +@dataclass +class trackCallbackObject: + """ + Track callback object that combines trackObject with status-specific fields. + Used for progress reporting during track processing. + """ + track: trackObject = field(default_factory=trackObject) + status_info: Union[ + initializingObject, + skippedObject, + retryingObject, + realTimeObject, + errorObject, + doneObject + ] = field(default_factory=initializingObject) + current_track: Optional[int] = None + total_tracks: Optional[int] = None + parent: Optional[Union[albumTrackObject, playlistTrackObject]] = None + + +@dataclass +class albumCallbackObject: + """ + Album callback object that combines albumObject with status-specific fields. + Used for progress reporting during album processing. + """ + album: albumObject = field(default_factory=albumObject) + status_info: Union[ + initializingObject, + skippedObject, + retryingObject, + realTimeObject, + errorObject, + doneObject + ] = field(default_factory=initializingObject) + + +@dataclass +class playlistCallbackObject: + """ + Playlist callback object that combines playlistObject with status-specific fields. + Used for progress reporting during playlist processing. + """ + playlist: playlistObject = field(default_factory=playlistObject) + status_info: Union[ + initializingObject, + skippedObject, + retryingObject, + realTimeObject, + errorObject, + doneObject + ] = field(default_factory=initializingObject) \ No newline at end of file diff --git a/deezspot/models/callback/playlist.py b/deezspot/models/callback/playlist.py index 7b67123..b297e24 100644 --- a/deezspot/models/callback/playlist.py +++ b/deezspot/models/callback/playlist.py @@ -4,8 +4,14 @@ from dataclasses import dataclass, field from typing import List, Optional, Dict, Any from .common import IDs -from .artist import artistObject +from .user import userObject +@dataclass +class artistAlbumTrackPlaylistObject: + """Artist when nested inside a track in a playlist context.""" + type: str = "artistAlbumTrackPlaylist" + name: str = "" + ids: IDs = field(default_factory=IDs) @dataclass class albumTrackPlaylistObject: @@ -15,6 +21,7 @@ class albumTrackPlaylistObject: title: str = "" release_date: Dict[str, Any] = field(default_factory=dict) # ReleaseDate as dict ids: IDs = field(default_factory=IDs) + artists: List[artistAlbumTrackPlaylistObject] = field(default_factory=list) @dataclass @@ -22,7 +29,6 @@ class artistTrackPlaylistObject: """Artist when nested inside a track in a playlist context.""" type: str = "artistTrackPlaylist" name: str = "" - genres: List[str] = field(default_factory=list) ids: IDs = field(default_factory=IDs) @@ -33,10 +39,11 @@ class trackPlaylistObject: title: str = "" position: int = 0 # Position in the playlist duration_ms: int = 0 # mandatory - # Nested objects instead of string references - artist: artistTrackPlaylistObject = field(default_factory=artistTrackPlaylistObject) + artists: List[artistTrackPlaylistObject] = field(default_factory=list) album: albumTrackPlaylistObject = field(default_factory=albumTrackPlaylistObject) ids: IDs = field(default_factory=IDs) + disc_number: int = 1 + track_number: int = 1 @dataclass @@ -45,7 +52,6 @@ class playlistObject: type: str = "playlist" title: str = "" description: Optional[str] = None - collaborative: bool = False - owner: artistObject = field(default_factory=artistObject) + owner: userObject = field(default_factory=userObject) tracks: List[trackPlaylistObject] = field(default_factory=list) ids: IDs = field(default_factory=IDs) \ No newline at end of file diff --git a/deezspot/models/callback/track.py b/deezspot/models/callback/track.py index f0260de..cf0ba09 100644 --- a/deezspot/models/callback/track.py +++ b/deezspot/models/callback/track.py @@ -1,12 +1,48 @@ #!/usr/bin/python3 from dataclasses import dataclass, field -from typing import List, Optional +from typing import List, Optional, Dict, Any from .common import IDs -from .album import albumTrackObject -from .artist import artistTrackObject +from .user import userObject +@dataclass +class artistAlbumTrackObject: + """Artist when nested inside a track in an album context.""" + type: str = "artistAlbumTrack" + name: str = "" + ids: IDs = field(default_factory=IDs) + +@dataclass +class artistTrackObject: + """ + An artist when nested inside a track context. + No genres, no albums—just identifying info. + """ + type: str = "artistTrack" + name: str = "" + ids: IDs = field(default_factory=IDs) + +@dataclass +class albumTrackObject: + """Album when nested inside a track context.""" + type: str = "albumTrack" + album_type: str = "" # "album" | "single" | "compilation" + title: str = "" + release_date: Dict[str, Any] = field(default_factory=dict) # ReleaseDate as dict + total_tracks: int = 0 + genres: List[str] = field(default_factory=list) + ids: IDs = field(default_factory=IDs) + artists: List[artistAlbumTrackObject] = field(default_factory=list) + +@dataclass +class playlistTrackObject: + """Playlist when nested inside a track context.""" + type: str = "playlistTrack" + title: str = "" + description: Optional[str] = None + owner: userObject = field(default_factory=userObject) + ids: IDs = field(default_factory=IDs) @dataclass class trackObject: @@ -17,11 +53,6 @@ class trackObject: track_number: int = 1 duration_ms: int = 0 # mandatory genres: List[str] = field(default_factory=list) - - # Nested album summary album: albumTrackObject = field(default_factory=albumTrackObject) - - # Nested lean artist summary (no genres/albums) - artist: artistTrackObject = field(default_factory=artistTrackObject) - - ids: IDs = field(default_factory=IDs) \ No newline at end of file + artists: List[artistTrackObject] = field(default_factory=list) + ids: IDs = field(default_factory=IDs) \ No newline at end of file diff --git a/deezspot/models/callback/user.py b/deezspot/models/callback/user.py new file mode 100644 index 0000000..a97f493 --- /dev/null +++ b/deezspot/models/callback/user.py @@ -0,0 +1,14 @@ +#!/usr/bin/python3 + +from dataclasses import dataclass, field +from typing import Optional + +from .common import IDs + + +@dataclass +class userObject: + """A user object representation.""" + name: str = "" + type: str = "user" + ids: IDs = field(default_factory=IDs) diff --git a/deezspot/models/download/__init__.py b/deezspot/models/download/__init__.py new file mode 100644 index 0000000..a6eeb34 --- /dev/null +++ b/deezspot/models/download/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/python3 + +from deezspot.models.download.smart import Smart +from deezspot.models.download.track import Track +from deezspot.models.download.album import Album +from deezspot.models.download.playlist import Playlist +from deezspot.models.download.preferences import Preferences +from deezspot.models.download.episode import Episode diff --git a/deezspot/models/album.py b/deezspot/models/download/album.py similarity index 88% rename from deezspot/models/album.py rename to deezspot/models/download/album.py index 6cfb958..daf51d3 100644 --- a/deezspot/models/album.py +++ b/deezspot/models/download/album.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 -from deezspot.models.track import Track +from deezspot.models.download.track import Track class Album: def __init__(self, ids: int) -> None: diff --git a/deezspot/models/episode.py b/deezspot/models/download/episode.py similarity index 100% rename from deezspot/models/episode.py rename to deezspot/models/download/episode.py diff --git a/deezspot/models/playlist.py b/deezspot/models/download/playlist.py similarity index 71% rename from deezspot/models/playlist.py rename to deezspot/models/download/playlist.py index 53c9aa7..e47ba31 100644 --- a/deezspot/models/playlist.py +++ b/deezspot/models/download/playlist.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 -from deezspot.models.track import Track +from deezspot.models.download.track import Track class Playlist: def __init__(self) -> None: diff --git a/deezspot/models/preferences.py b/deezspot/models/download/preferences.py similarity index 100% rename from deezspot/models/preferences.py rename to deezspot/models/download/preferences.py diff --git a/deezspot/models/smart.py b/deezspot/models/download/smart.py similarity index 55% rename from deezspot/models/smart.py rename to deezspot/models/download/smart.py index 38a980e..7abe480 100644 --- a/deezspot/models/smart.py +++ b/deezspot/models/download/smart.py @@ -1,8 +1,8 @@ #!/usr/bin/python3 -from deezspot.models.track import Track -from deezspot.models.album import Album -from deezspot.models.playlist import Playlist +from deezspot.models.download.track import Track +from deezspot.models.download.album import Album +from deezspot.models.download.playlist import Playlist class Smart: def __init__(self) -> None: diff --git a/deezspot/models/track.py b/deezspot/models/download/track.py similarity index 100% rename from deezspot/models/track.py rename to deezspot/models/download/track.py diff --git a/deezspot/spotloader/__download__.py b/deezspot/spotloader/__download__.py index 913fd77..b2b22a4 100644 --- a/deezspot/spotloader/__download__.py +++ b/deezspot/spotloader/__download__.py @@ -17,7 +17,7 @@ from os import ( system, replace as os_replace, ) -from deezspot.models import ( +from deezspot.models.download import ( Track, Album, Playlist, @@ -38,6 +38,27 @@ from deezspot.libutils.cleanup_utils import ( unregister_active_download, ) from deezspot.libutils.skip_detection import check_track_exists +from deezspot.models.callback import ( + trackCallbackObject, + albumCallbackObject, + playlistCallbackObject, + initializingObject, + skippedObject, + retryingObject, + realTimeObject, + errorObject, + doneObject, + summaryObject, + failedTrackObject, + trackObject as cb_trackObject, + albumObject as cb_albumObject, + playlistObject as cb_playlistObject, + artistTrackObject, + albumTrackObject, + playlistTrackObject, + userObject, + IDs +) # --- Global retry counter variables --- GLOBAL_RETRY_COUNT = 0 @@ -352,48 +373,90 @@ class EASY_DW: self.__c_track.was_skipped = True parent_info, total_tracks_val = self._get_parent_info() - - summary_data = { - "successful_tracks": [], - "skipped_tracks": [f"{current_title} - {current_artist}"], - "failed_tracks": [], - "total_successful": 0, - "total_skipped": 1, - "total_failed": 0, - } if self.__parent is None else None + + track_for_callback = cb_trackObject( + title=current_title, + artists=[artistTrackObject(name=artist.strip()) for artist in current_artist.split(';')], + ids=IDs(spotify=self.__ids) + ) + + initializing_status = initializingObject( + ids=IDs(spotify=self.__ids), + convert_to=self.__preferences.convert_to, + bitrate=self.__preferences.bitrate + ) + + parent_for_callback = None + if parent_info: + if parent_info.get("type") == "album": + parent_for_callback = albumTrackObject( + title=parent_info.get("title"), + album_type=self.__song_metadata.get("album_type", "album"), + total_tracks=parent_info.get("total_tracks"), + ids=IDs(spotify=self.__song_metadata.get("album_id")) + ) + elif parent_info.get("type") == "playlist": + parent_for_callback = playlistTrackObject( + title=parent_info.get("name"), + owner=userObject(name=parent_info.get("owner")), + ids=IDs(spotify=parent_info.get("url", "").split("/")[-1]) + ) + + callback_object = trackCallbackObject( + track=track_for_callback, + status_info=initializing_status, + current_track=getattr(self.__preferences, 'track_number', None), + total_tracks=total_tracks_val, + parent=parent_for_callback + ) report_progress( reporter=Download_JOB.progress_reporter, - report_type="track", - status="skipped", - song=current_title, - artist=current_artist, - url=self.__link, - reason=f"Track already exists in desired format at {existing_file_path}", - convert_to=self.__preferences.convert_to, - bitrate=self.__preferences.bitrate, - current_track=getattr(self.__preferences, 'track_number', None), - total_tracks=total_tracks_val, - parent=parent_info, - summary=summary_data + callback_obj=callback_object ) return self.__c_track # Report initializing status for the track download parent_info, total_tracks_val = self._get_parent_info() - report_progress( - reporter=Download_JOB.progress_reporter, - report_type="track", - status="initializing", - song=current_title, - artist=current_artist, - album=current_album, - url=self.__link, + track_for_callback = cb_trackObject( + title=current_title, + artists=[artistTrackObject(name=artist.strip()) for artist in current_artist.split(';')], + ids=IDs(spotify=self.__ids) + ) + + initializing_status = initializingObject( + ids=IDs(spotify=self.__ids), convert_to=self.__preferences.convert_to, - bitrate=self.__preferences.bitrate, - parent=parent_info, + bitrate=self.__preferences.bitrate + ) + + parent_for_callback = None + if parent_info: + if parent_info.get("type") == "album": + parent_for_callback = albumTrackObject( + title=parent_info.get("title"), + album_type=self.__song_metadata.get("album_type", "album"), + total_tracks=parent_info.get("total_tracks"), + ids=IDs(spotify=self.__song_metadata.get("album_id")) + ) + elif parent_info.get("type") == "playlist": + parent_for_callback = playlistTrackObject( + title=parent_info.get("name"), + owner=userObject(name=parent_info.get("owner")), + ids=IDs(spotify=parent_info.get("url", "").split("/")[-1]) + ) + + callback_object = trackCallbackObject( + track=track_for_callback, + status_info=initializing_status, current_track=getattr(self.__preferences, 'track_number', None), total_tracks=total_tracks_val, + parent=parent_for_callback + ) + + report_progress( + reporter=Download_JOB.progress_reporter, + callback_obj=callback_object ) # If track does not exist in the desired final format, proceed with download/conversion @@ -449,20 +512,31 @@ class EASY_DW: if current_percentage > self._last_reported_percentage: self._last_reported_percentage = current_percentage - report_progress( - reporter=Download_JOB.progress_reporter, - report_type="track", - status="real-time", - song=self.__song_metadata.get("music", ""), - artist=self.__song_metadata.get("artist", ""), - url=self.__link, - time_elapsed=int((current_time - start_time) * 1000), - progress=current_percentage, + real_time_status = realTimeObject( + ids=IDs(spotify=self.__ids), convert_to=self.__convert_to, bitrate=self.__bitrate, + time_elapsed=int((current_time - start_time) * 1000), + progress=current_percentage + ) + + track_for_callback = cb_trackObject( + title=self.__song_metadata.get("music", ""), + artists=[artistTrackObject(name=artist.strip()) for artist in self.__song_metadata.get("artist", "").split(';')], + ids=IDs(spotify=self.__ids) + ) + + callback_object = trackCallbackObject( + track=track_for_callback, + status_info=real_time_status, current_track=getattr(self.__preferences, 'track_number', None), total_tracks=total_tracks_val, - parent=parent_info + parent=parent_for_callback + ) + + report_progress( + reporter=Download_JOB.progress_reporter, + callback_obj=callback_object ) # Rate limiting (if needed) @@ -570,28 +644,34 @@ class EASY_DW: } }) - report_progress( - reporter=Download_JOB.progress_reporter, - report_type="track", - status="retrying", - retry_count=retries, - seconds_left=retry_delay, - song=self.__song_metadata.get('music', ''), - artist=self.__song_metadata.get('artist', ''), - album=self.__song_metadata.get('album', ''), - error=str(e), - url=self.__link, + retrying_status = retryingObject( + ids=IDs(spotify=self.__ids), convert_to=self.__convert_to, bitrate=self.__bitrate, - current_track=getattr(self.__preferences, 'track_number', None), - total_tracks=total_tracks_val, - parent=parent_info + retry_count=retries, + seconds_left=retry_delay, + error=str(e) + ) + + track_for_callback = cb_trackObject( + title=self.__song_metadata.get('music', 'Unknown Episode'), + artists=[artistTrackObject(name=self.__song_metadata.get('artist', 'Unknown Show'))], + ids=IDs(spotify=self.__ids) + ) + + callback_object = trackCallbackObject( + track=track_for_callback, + status_info=retrying_status + ) + + report_progress( + reporter=Download_JOB.progress_reporter, + callback_obj=callback_object ) if retries >= max_retries or GLOBAL_RETRY_COUNT >= GLOBAL_MAX_RETRIES: - # Final cleanup before giving up if os.path.exists(self.__song_path): - os.remove(self.__song_path) + os.remove(self.__song_path) # Clean up partial file # Add track info to exception track_name = self.__song_metadata.get('music', 'Unknown Track') artist_name = self.__song_metadata.get('artist', 'Unknown Artist') @@ -626,20 +706,28 @@ class EASY_DW: else: error_msg = f"Audio conversion failed: {original_error_str}" - report_progress( - reporter=Download_JOB.progress_reporter, - report_type="track", - status="error", - song=self.__song_metadata.get('music', ''), - artist=self.__song_metadata.get('artist', ''), - error=error_msg, - url=self.__link, + error_status = errorObject( + ids=IDs(spotify=self.__ids), convert_to=self.__convert_to, bitrate=self.__bitrate, + error=error_msg + ) + track_for_callback = cb_trackObject( + title=self.__song_metadata.get('music', ''), + artists=[artistTrackObject(name=artist.strip()) for artist in self.__song_metadata.get('artist', '').split(';')], + ids=IDs(spotify=self.__ids) + ) + callback_object = trackCallbackObject( + track=track_for_callback, + status_info=error_status, current_track=getattr(self.__preferences, 'track_number', None), total_tracks=total_tracks_val, parent=parent_info ) + report_progress( + reporter=Download_JOB.progress_reporter, + callback_obj=callback_object + ) logger.error(f"Audio conversion error: {error_msg}") # If conversion fails, clean up the .ogg file @@ -654,19 +742,27 @@ class EASY_DW: except Exception as conv_e: # If conversion fails twice, create a final error report error_msg_2 = f"Audio conversion failed after retry for '{self.__song_metadata.get('music', 'Unknown Track')}'. Original error: {str(conv_e)}" - report_progress( - reporter=Download_JOB.progress_reporter, - report_type="track", - status="error", - song=self.__song_metadata.get('music', 'Unknown Track'), - artist=self.__song_metadata.get('artist', ''), - error=error_msg_2, - url=self.__link, + error_status_2 = errorObject( + ids=IDs(spotify=self.__ids), convert_to=self.__convert_to, bitrate=self.__bitrate, - parent=parent_info, + error=error_msg_2 + ) + track_for_callback_2 = cb_trackObject( + title=self.__song_metadata.get('music', 'Unknown Track'), + artists=[artistTrackObject(name=artist.strip()) for artist in self.__song_metadata.get('artist', '').split(';')], + ids=IDs(spotify=self.__ids) + ) + callback_object_2 = trackCallbackObject( + track=track_for_callback_2, + status_info=error_status_2, current_track=getattr(self.__preferences, 'track_number', None), - total_tracks=total_tracks_val + total_tracks=total_tracks_val, + parent=parent_info + ) + report_progress( + reporter=Download_JOB.progress_reporter, + callback_obj=callback_object_2 ) logger.error(error_msg) @@ -697,29 +793,38 @@ class EASY_DW: total_tracks_val = self.__song_metadata.get('nb_tracks', 0) current_track_val = getattr(self.__preferences, 'track_number', 0) + summary_obj = None if self.__parent is None: - summary_data = { - "successful_tracks": [f"{song} - {artist}"], - "skipped_tracks": [], - "failed_tracks": [], - "total_successful": 1, - "total_skipped": 0, - "total_failed": 0, - } + track_obj = cb_trackObject(title=f"{song} - {artist}") + summary_obj = summaryObject( + successful_tracks=[track_obj], + total_successful=1 + ) + + done_status = doneObject( + ids=IDs(spotify=self.__ids), + convert_to=self.__convert_to, + bitrate=self.__bitrate, + summary=summary_obj + ) + + track_for_callback = cb_trackObject( + title=song, + artists=[artistTrackObject(name=a.strip()) for a in artist.split(';')], + ids=IDs(spotify=self.__ids) + ) + + callback_object = trackCallbackObject( + track=track_for_callback, + status_info=done_status, + current_track=current_track_val, + total_tracks=total_tracks_val, + parent=parent_for_callback + ) report_progress( reporter=Download_JOB.progress_reporter, - report_type="track", - status="done", - song=song, - artist=artist, - url=self.__link, - convert_to=self.__convert_to, - bitrate=self.__bitrate, - parent=parent_info, - current_track=current_track_val, - total_tracks=total_tracks_val, - summary=summary_data, + callback_obj=callback_object ) if hasattr(self, '_EASY_DW__c_track') and self.__c_track and self.__c_track.success: @@ -902,22 +1007,33 @@ class EASY_DW: # Or self.__song_path might be a partially converted file if convert_audio failed mid-way and didn't cleanup perfectly. episode_title = self.__song_metadata.get('music', 'Unknown Episode') error_message = f"Audio conversion for episode '{episode_title}' failed. Original error: {str(conv_e)}" + + track_for_callback = cb_trackObject( + title=episode_title, + artists=[artistTrackObject(name=self.__song_metadata.get('artist', 'Unknown Show'))], + ids=IDs(spotify=self.__ids) + ) + + error_status = errorObject( + ids=IDs(spotify=self.__ids), + convert_to=self.__convert_to, + bitrate=self.__bitrate, + error=error_message + ) + + callback_object = trackCallbackObject( + track=track_for_callback, + status_info=error_status + ) + report_progress( reporter=Download_JOB.progress_reporter, - report_type="episode", - status="error", - song=episode_title, - artist=self.__song_metadata.get('artist', 'Unknown Show'), - error=error_message, - url=self.__link, - convert_to=self.__convert_to, - bitrate=self.__bitrate + callback_obj=callback_object ) # Attempt to remove self.__song_path, which is the latest known path for this episode if os.path.exists(self.__song_path): os.remove(self.__song_path) - unregister_active_download(self.__song_path) # Unregister it as it failed/was removed - + logger.error(error_message) if hasattr(self, '_EASY_DW__c_episode') and self.__c_episode: self.__c_episode.success = False @@ -1005,157 +1121,54 @@ class DW_ALBUM: total_tracks = self.__song_metadata.get('nb_tracks', 0) album_id = self.__ids - report_progress( - reporter=Download_JOB.progress_reporter, - report_type="album", - artist=album_artist, - status="initializing", - total_tracks=total_tracks, - title=album_name, - url=f"https://open.spotify.com/album/{album_id}", - ) - - pic_url = self.__song_metadata['image'] # This is URL for spotify - image_bytes = request(pic_url).content - self.__song_metadata['image'] = image_bytes # Keep bytes for tagging - - album = Album(self.__ids) - album.image = image_bytes # Store raw image bytes for cover saving - album.nb_tracks = self.__song_metadata['nb_tracks'] - album.album_name = self.__song_metadata['album'] - album.upc = self.__song_metadata['upc'] - tracks = album.tracks - album.md5_image = self.__ids - album.tags = self.__song_metadata - - # Determine album base directory once - album_base_directory = get_album_directory( - self.__song_metadata, # Album level metadata - self.__output_dir, - custom_dir_format=self.__preferences.custom_dir_format, - pad_tracks=self.__preferences.pad_tracks - ) - - c_song_metadata = {} - for key, item in self.__song_metadata_items: - if key == 'popularity_list': - continue - if not isinstance(item, list): # Changed from type() to isinstance() - c_song_metadata[key] = item # Use item directly (it's self.__song_metadata[key]) - total_tracks = album.nb_tracks - for a in range(total_tracks): - for key, metadata_value_for_key in self.__song_metadata_items: # metadata_value_for_key is self.__song_metadata[key] - if isinstance(metadata_value_for_key, list): # Changed from type() is list to isinstance() - if key == 'popularity_list': - continue - if a < len(metadata_value_for_key): - c_song_metadata[key] = metadata_value_for_key[a] - else: - # Log a warning because a per-track list is shorter than expected. - # This was causing the IndexError. - album_name_for_log = c_song_metadata.get('album', self.__song_metadata.get('album', 'Unknown Album')) - logger.warning( - f"In album '{album_name_for_log}', metadata list for key '{key}' is too short. " - f"Expected at least {a + 1} elements for track {a + 1} " - f"(list has {len(metadata_value_for_key)}). Assigning None to '{key}' for this track." - ) - c_song_metadata[key] = None - song_name = c_song_metadata['music'] - artist_name = c_song_metadata['artist'] - album_name = c_song_metadata['album'] - current_track = a + 1 - - c_preferences = deepcopy(self.__preferences) - c_preferences.song_metadata = c_song_metadata.copy() - c_preferences.ids = c_song_metadata['ids'] - c_preferences.track_number = current_track # Track number in the album - c_preferences.link = f"https://open.spotify.com/track/{c_preferences.ids}" - - # Add album_id to song metadata for consistent parent info - c_preferences.song_metadata['album_id'] = self.__ids - - try: - # Use track-level reporting through EASY_DW - track = EASY_DW(c_preferences, parent='album').download_try() - except TrackNotFound as e_track: - track = Track(c_song_metadata, None, None, None, None, None) - track.success = False - track.error_message = str(e_track) # Store the error message from TrackNotFound - logger.warning(f"Track '{song_name}' by '{artist_name}' from album '{album.album_name}' not found or failed to download. Reason: {track.error_message}") - except Exception as e_generic: - track = Track(c_song_metadata, None, None, None, None, None) - track.success = False - track.error_message = f"An unexpected error occurred: {str(e_generic)}" - logger.error(f"Unexpected error downloading track '{song_name}' by '{artist_name}' from album '{album.album_name}'. Reason: {track.error_message}") - tracks.append(track) + successful_tracks_objs = [] + failed_tracks_objs = [] + skipped_tracks_objs = [] - # Save album cover image - if self.__preferences.save_cover and album.image and album_base_directory: - save_cover_image(album.image, album_base_directory, "cover.jpg") - - if self.__make_zip: - song_quality = tracks[0].quality - custom_dir_format = getattr(self.__preferences, 'custom_dir_format', None) - zip_name = create_zip( - tracks, - output_dir=self.__output_dir, - song_metadata=self.__song_metadata, - song_quality=song_quality, - custom_dir_format=custom_dir_format - ) - album.zip_path = zip_name - - # Report album done status - album_name = self.__song_metadata.get('album', 'Unknown Album') - - # Process album artist for the done status (use the same logic as initializing) - album_artist = self.__song_metadata.get('artist', 'Unknown Artist') - if isinstance(album_artist, list): - album_artist = most_frequent(album_artist) - elif isinstance(album_artist, str) and ";" in album_artist: - artists_list = [artist.strip() for artist in album_artist.split(";")] - album_artist = most_frequent(artists_list) if artists_list else album_artist - - total_tracks = self.__song_metadata.get('nb_tracks', 0) - album_id = self.__ids - - successful_tracks = [] - failed_tracks = [] - skipped_tracks = [] for track in tracks: - track_info = { - "name": track.tags.get('music', 'Unknown Track'), - "artist": track.tags.get('artist', 'Unknown Artist') - } + track_info = cb_trackObject( + title=track.tags.get('music', 'Unknown Track'), + artists=[artistTrackObject(name=artist.strip()) for artist in track.tags.get('artist', 'Unknown Artist').split(';')], + ids=IDs(spotify=track.tags.get('ids')) + ) if getattr(track, 'was_skipped', False): - skipped_tracks.append(track_info) + skipped_tracks_objs.append(track_info) elif track.success: - successful_tracks.append(track_info) + successful_tracks_objs.append(track_info) else: - track_info["reason"] = getattr(track, 'error_message', 'Unknown reason') - failed_tracks.append(track_info) + reason = getattr(track, 'error_message', 'Unknown reason') + failed_tracks_objs.append(failedTrackObject(track=track_info, reason=reason)) - summary = { - "successful_tracks": [f"{t['name']} - {t['artist']}" for t in successful_tracks], - "skipped_tracks": [f"{t['name']} - {t['artist']}" for t in skipped_tracks], - "failed_tracks": [{ - "track": f"{t['name']} - {t['artist']}", - "reason": t['reason'] - } for t in failed_tracks], - "total_successful": len(successful_tracks), - "total_skipped": len(skipped_tracks), - "total_failed": len(failed_tracks) - } + summary = summaryObject( + successful_tracks=successful_tracks_objs, + skipped_tracks=skipped_tracks_objs, + failed_tracks=failed_tracks_objs, + total_successful=len(successful_tracks_objs), + total_skipped=len(skipped_tracks_objs), + total_failed=len(failed_tracks_objs) + ) + + done_status = doneObject( + ids=IDs(spotify=album_id), + summary=summary + ) + + album_for_callback = cb_albumObject( + title=album_name, + artists=[artistTrackObject(name=album_artist.strip())], + total_tracks=total_tracks, + ids=IDs(spotify=album_id), + album_type=self.__song_metadata.get("album_type", "album") + ) + + callback_object = albumCallbackObject( + album=album_for_callback, + status_info=done_status + ) report_progress( reporter=Download_JOB.progress_reporter, - report_type="album", - artist=album_artist, - status="done", - total_tracks=total_tracks, - title=album_name, - url=f"https://open.spotify.com/album/{album_id}", - summary=summary, + callback_obj=callback_object ) return album @@ -1179,14 +1192,25 @@ class DW_PLAYLIST: playlist_id = self.__ids # Report playlist initializing status + playlist_for_callback = cb_playlistObject( + title=playlist_name, + owner=userObject(name=playlist_owner), + ids=IDs(spotify=playlist_id), + description=self.__json_data.get('description') + ) + + initializing_status = initializingObject( + ids=IDs(spotify=playlist_id) + ) + + callback_object = playlistCallbackObject( + playlist=playlist_for_callback, + status_info=initializing_status + ) + report_progress( reporter=Download_JOB.progress_reporter, - report_type="playlist", - owner=playlist_owner, - status="initializing", - total_tracks=total_tracks, - name=playlist_name, - url=f"https://open.spotify.com/playlist/{playlist_id}", + callback_obj=callback_object ) # --- Prepare the m3u playlist file --- @@ -1336,42 +1360,52 @@ class DW_PLAYLIST: total_tracks = self.__json_data.get('tracks', {}).get('total', 0) playlist_id = self.__ids - successful_tracks = [] - failed_tracks = [] - skipped_tracks = [] + successful_tracks_objs = [] + failed_tracks_objs = [] + skipped_tracks_objs = [] for track in tracks: - track_info = { - "name": track.tags.get('music') or track.tags.get('name', 'Unknown Track'), - "artist": track.tags.get('artist', 'Unknown Artist') - } + track_info = cb_trackObject( + title=track.tags.get('music') or track.tags.get('name', 'Unknown Track'), + artists=[artistTrackObject(name=artist.strip()) for artist in track.tags.get('artist', 'Unknown Artist').split(';')], + ids=IDs(spotify=track.tags.get('ids')) + ) if getattr(track, 'was_skipped', False): - skipped_tracks.append(track_info) + skipped_tracks_objs.append(track_info) elif track.success: - successful_tracks.append(track_info) + successful_tracks_objs.append(track_info) else: - track_info["reason"] = getattr(track, 'error_message', 'Unknown reason') - failed_tracks.append(track_info) + reason = getattr(track, 'error_message', 'Unknown reason') + failed_tracks_objs.append(failedTrackObject(track=track_info, reason=reason)) + + summary = summaryObject( + successful_tracks=successful_tracks_objs, + skipped_tracks=skipped_tracks_objs, + failed_tracks=failed_tracks_objs, + total_successful=len(successful_tracks_objs), + total_skipped=len(skipped_tracks_objs), + total_failed=len(failed_tracks_objs) + ) + + done_status = doneObject( + ids=IDs(spotify=playlist_id), + summary=summary + ) + + playlist_for_callback = cb_playlistObject( + title=playlist_name, + owner=userObject(name=playlist_owner), + ids=IDs(spotify=playlist_id), + description=self.__json_data.get('description') + ) + + callback_object = playlistCallbackObject( + playlist=playlist_for_callback, + status_info=done_status + ) - summary = { - "successful_tracks": [f"{t['name']} - {t['artist']}" for t in successful_tracks], - "skipped_tracks": [f"{t['name']} - {t['artist']}" for t in skipped_tracks], - "failed_tracks": [{ - "track": f"{t['name']} - {t['artist']}", - "reason": t['reason'] - } for t in failed_tracks], - "total_successful": len(successful_tracks), - "total_skipped": len(skipped_tracks), - "total_failed": len(failed_tracks) - } report_progress( reporter=Download_JOB.progress_reporter, - report_type="playlist", - owner=playlist_owner, - status="done", - total_tracks=total_tracks, - name=playlist_name, - url=f"https://open.spotify.com/playlist/{playlist_id}", - summary=summary, + callback_obj=callback_object ) return playlist @@ -1385,26 +1419,41 @@ class DW_EPISODE: def dw(self) -> Episode: episode_id = self.__preferences.ids - url = f"https://open.spotify.com/episode/{episode_id}" if episode_id else None + + track_for_callback = cb_trackObject( + title=self.__preferences.song_metadata.get('name', 'Unknown Episode'), + artists=[artistTrackObject(name=self.__preferences.song_metadata.get('show', 'Unknown Show'))], + ids=IDs(spotify=episode_id) + ) + + initializing_status = initializingObject( + ids=IDs(spotify=episode_id) + ) + + callback_object_init = trackCallbackObject( + track=track_for_callback, + status_info=initializing_status + ) report_progress( reporter=Download_JOB.progress_reporter, - report_type="episode", - song=self.__preferences.song_metadata.get('name', 'Unknown Episode'), - artist=self.__preferences.song_metadata.get('show', 'Unknown Show'), - status="initializing", - url=url, + callback_obj=callback_object_init ) episode = EASY_DW(self.__preferences).download_eps() + done_status = doneObject( + ids=IDs(spotify=episode_id) + ) + + callback_object_done = trackCallbackObject( + track=track_for_callback, + status_info=done_status + ) + report_progress( reporter=Download_JOB.progress_reporter, - report_type="episode", - song=self.__preferences.song_metadata.get('name', 'Unknown Episode'), - artist=self.__preferences.song_metadata.get('show', 'Unknown Show'), - status="done", - url=url, + callback_obj=callback_object_done ) return episode diff --git a/deezspot/spotloader/__init__.py b/deezspot/spotloader/__init__.py index 85eda30..2776bcd 100644 --- a/deezspot/spotloader/__init__.py +++ b/deezspot/spotloader/__init__.py @@ -11,7 +11,7 @@ from deezspot.libutils.utils import ( link_is_valid, what_kind, ) -from deezspot.models import ( +from deezspot.models.download import ( Track, Album, Playlist,