implemented typechecking in spotloader
This commit is contained in:
@@ -104,6 +104,31 @@ class Spo:
|
||||
|
||||
return track_json
|
||||
|
||||
@classmethod
|
||||
def get_tracks(cls, ids: list, market: str = None, client_id=None, client_secret=None):
|
||||
"""
|
||||
Get information for multiple tracks by a list of IDs.
|
||||
|
||||
Args:
|
||||
ids (list): A list of Spotify track IDs.
|
||||
market (str, optional): An ISO 3166-1 alpha-2 country code.
|
||||
client_id (str, optional): Optional custom Spotify client ID.
|
||||
client_secret (str, optional): Optional custom Spotify client secret.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing a list of track information.
|
||||
"""
|
||||
api = cls.__get_api(client_id, client_secret)
|
||||
try:
|
||||
tracks_json = api.tracks(ids, market=market)
|
||||
except SpotifyException as error:
|
||||
if error.http_status in cls.__error_codes:
|
||||
# Create a string of the first few IDs for the error message
|
||||
ids_preview = ', '.join(ids[:3]) + ('...' if len(ids) > 3 else '')
|
||||
raise InvalidLink(f"one or more IDs in the list: [{ids_preview}]")
|
||||
|
||||
return tracks_json
|
||||
|
||||
@classmethod
|
||||
def get_album(cls, ids, client_id=None, client_secret=None):
|
||||
"""
|
||||
|
||||
@@ -4,6 +4,7 @@ import logging
|
||||
import sys
|
||||
from typing import Optional, Callable, Dict, Any, Union
|
||||
import json
|
||||
from dataclasses import asdict
|
||||
|
||||
from deezspot.models.callback.callbacks import (
|
||||
BaseStatusObject,
|
||||
@@ -165,7 +166,8 @@ def report_progress(
|
||||
raise TypeError(f"callback_obj must be of type trackCallbackObject, albumCallbackObject, or playlistCallbackObject, got {type(callback_obj)}")
|
||||
|
||||
# Convert the callback object to a dictionary and report it
|
||||
report_dict = asdict(callback_obj)
|
||||
if reporter:
|
||||
reporter.report(callback_obj.__dict__)
|
||||
reporter.report(report_dict)
|
||||
else:
|
||||
logger.info(json.dumps(callback_obj.__dict__))
|
||||
logger.info(json.dumps(report_dict))
|
||||
@@ -6,7 +6,7 @@ Callback data models for the music metadata schema.
|
||||
|
||||
from .common import IDs, ReleaseDate
|
||||
from .artist import artistObject, albumArtistObject
|
||||
from .album import albumObject, trackAlbumObject
|
||||
from .album import albumObject, trackAlbumObject, artistAlbumObject
|
||||
from .track import trackObject, artistTrackObject, albumTrackObject, playlistTrackObject
|
||||
from .playlist import playlistObject, trackPlaylistObject, albumTrackPlaylistObject, artistTrackPlaylistObject
|
||||
from .callbacks import (
|
||||
@@ -22,4 +22,5 @@ from .callbacks import (
|
||||
trackCallbackObject,
|
||||
albumCallbackObject,
|
||||
playlistCallbackObject
|
||||
)
|
||||
)
|
||||
from .user import userObject
|
||||
@@ -29,6 +29,7 @@ class trackAlbumObject:
|
||||
disc_number: int = 1
|
||||
track_number: int = 1
|
||||
duration_ms: int = 0
|
||||
explicit: bool = False
|
||||
genres: List[str] = field(default_factory=list)
|
||||
ids: IDs = field(default_factory=IDs)
|
||||
artists: List[artistTrackAlbumObject] = field(default_factory=list)
|
||||
@@ -43,6 +44,8 @@ class albumObject:
|
||||
release_date: Dict[str, Any] = field(default_factory=dict)
|
||||
total_tracks: int = 0
|
||||
genres: List[str] = field(default_factory=list)
|
||||
images: List[Dict[str, Any]] = field(default_factory=list)
|
||||
copyrights: List[Dict[str, str]] = field(default_factory=list)
|
||||
ids: IDs = field(default_factory=IDs)
|
||||
tracks: List[trackAlbumObject] = field(default_factory=list)
|
||||
artists: List[artistAlbumObject] = field(default_factory=list)
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Dict, Any
|
||||
|
||||
from .common import IDs
|
||||
|
||||
@@ -23,5 +23,6 @@ class artistObject:
|
||||
type: str = "artist"
|
||||
name: str = ""
|
||||
genres: List[str] = field(default_factory=list)
|
||||
images: List[Dict[str, Any]] = field(default_factory=list)
|
||||
ids: IDs = field(default_factory=IDs)
|
||||
albums: List[albumArtistObject] = field(default_factory=list)
|
||||
@@ -10,6 +10,7 @@ class IDs:
|
||||
spotify: Optional[str] = None
|
||||
deezer: Optional[str] = None
|
||||
isrc: Optional[str] = None
|
||||
upc: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@@ -20,6 +20,8 @@ class albumTrackPlaylistObject:
|
||||
album_type: str = "" # "album" | "single" | "compilation"
|
||||
title: str = ""
|
||||
release_date: Dict[str, Any] = field(default_factory=dict) # ReleaseDate as dict
|
||||
total_tracks: int = 0
|
||||
images: List[Dict[str, Any]] = field(default_factory=list)
|
||||
ids: IDs = field(default_factory=IDs)
|
||||
artists: List[artistAlbumTrackPlaylistObject] = field(default_factory=list)
|
||||
|
||||
@@ -54,4 +56,5 @@ class playlistObject:
|
||||
description: Optional[str] = None
|
||||
owner: userObject = field(default_factory=userObject)
|
||||
tracks: List[trackPlaylistObject] = field(default_factory=list)
|
||||
images: List[Dict[str, Any]] = field(default_factory=list)
|
||||
ids: IDs = field(default_factory=IDs)
|
||||
@@ -32,6 +32,7 @@ class albumTrackObject:
|
||||
release_date: Dict[str, Any] = field(default_factory=dict) # ReleaseDate as dict
|
||||
total_tracks: int = 0
|
||||
genres: List[str] = field(default_factory=list)
|
||||
images: List[Dict[str, Any]] = field(default_factory=list)
|
||||
ids: IDs = field(default_factory=IDs)
|
||||
artists: List[artistAlbumTrackObject] = field(default_factory=list)
|
||||
|
||||
@@ -52,6 +53,7 @@ class trackObject:
|
||||
disc_number: int = 1
|
||||
track_number: int = 1
|
||||
duration_ms: int = 0 # mandatory
|
||||
explicit: bool = False
|
||||
genres: List[str] = field(default_factory=list)
|
||||
album: albumTrackObject = field(default_factory=albumTrackObject)
|
||||
artists: List[artistTrackObject] = field(default_factory=list)
|
||||
|
||||
@@ -8,6 +8,7 @@ class Preferences:
|
||||
self.output_dir = None
|
||||
self.ids = None
|
||||
self.json_data = None
|
||||
self.playlist_tracks_json = None
|
||||
self.recursive_quality = None
|
||||
self.recursive_download = None
|
||||
self.not_interface = None
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,6 +19,7 @@ from deezspot.models.download import (
|
||||
Smart,
|
||||
Episode
|
||||
)
|
||||
from deezspot.models.callback import trackCallbackObject, errorObject
|
||||
from deezspot.spotloader.__download__ import (
|
||||
DW_TRACK,
|
||||
DW_ALBUM,
|
||||
@@ -98,6 +99,7 @@ class SpoLogin:
|
||||
save_cover=stock_save_cover,
|
||||
market: list[str] | None = stock_market
|
||||
) -> Track:
|
||||
song_metadata = None
|
||||
try:
|
||||
link_is_valid(link_track)
|
||||
ids = get_ids(link_track)
|
||||
@@ -106,7 +108,7 @@ class SpoLogin:
|
||||
if song_metadata is None:
|
||||
raise Exception(f"Could not retrieve metadata for track {link_track}. It might not be available or an API error occurred.")
|
||||
|
||||
logger.info(f"Starting download for track: {song_metadata.get('music', 'Unknown')} - {song_metadata.get('artist', 'Unknown')}")
|
||||
logger.info(f"Starting download for track: {song_metadata.title} - {'; '.join([a.name for a in song_metadata.artists])}")
|
||||
|
||||
preferences = Preferences()
|
||||
preferences.real_time_dl = real_time_dl
|
||||
@@ -140,60 +142,22 @@ class SpoLogin:
|
||||
except MarketAvailabilityError as e:
|
||||
logger.error(f"Track download failed due to market availability: {str(e)}")
|
||||
if song_metadata:
|
||||
track_info = {
|
||||
"name": song_metadata.get("music", "Unknown Track"),
|
||||
"artist": song_metadata.get("artist", "Unknown Artist"),
|
||||
}
|
||||
summary = {
|
||||
"successful_tracks": [],
|
||||
"skipped_tracks": [],
|
||||
"failed_tracks": [{
|
||||
"track": f"{track_info['name']} - {track_info['artist']}",
|
||||
"reason": str(e)
|
||||
}],
|
||||
"total_successful": 0,
|
||||
"total_skipped": 0,
|
||||
"total_failed": 1
|
||||
}
|
||||
status_obj = errorObject(ids=song_metadata.ids, error=str(e))
|
||||
callback_obj = trackCallbackObject(track=song_metadata, status_info=status_obj)
|
||||
report_progress(
|
||||
reporter=self.progress_reporter,
|
||||
report_type="track",
|
||||
song=track_info['name'],
|
||||
artist=track_info['artist'],
|
||||
status="error",
|
||||
url=link_track,
|
||||
error=str(e),
|
||||
summary=summary
|
||||
callback_obj=callback_obj
|
||||
)
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to download track: {str(e)}")
|
||||
traceback.print_exc()
|
||||
if song_metadata:
|
||||
track_info = {
|
||||
"name": song_metadata.get("music", "Unknown Track"),
|
||||
"artist": song_metadata.get("artist", "Unknown Artist"),
|
||||
}
|
||||
summary = {
|
||||
"successful_tracks": [],
|
||||
"skipped_tracks": [],
|
||||
"failed_tracks": [{
|
||||
"track": f"{track_info['name']} - {track_info['artist']}",
|
||||
"reason": str(e)
|
||||
}],
|
||||
"total_successful": 0,
|
||||
"total_skipped": 0,
|
||||
"total_failed": 1
|
||||
}
|
||||
status_obj = errorObject(ids=song_metadata.ids, error=str(e))
|
||||
callback_obj = trackCallbackObject(track=song_metadata, status_info=status_obj)
|
||||
report_progress(
|
||||
reporter=self.progress_reporter,
|
||||
report_type="track",
|
||||
song=track_info['name'],
|
||||
artist=track_info['artist'],
|
||||
status="error",
|
||||
url=link_track,
|
||||
error=str(e),
|
||||
summary=summary
|
||||
callback_obj=callback_obj
|
||||
)
|
||||
raise e
|
||||
|
||||
@@ -228,7 +192,7 @@ class SpoLogin:
|
||||
if song_metadata is None:
|
||||
raise Exception(f"Could not process album metadata for {link_album}. It might not be available in the specified market(s) or an API error occurred.")
|
||||
|
||||
logger.info(f"Starting download for album: {song_metadata.get('album', 'Unknown')} - {song_metadata.get('ar_album', 'Unknown')}")
|
||||
logger.info(f"Starting download for album: {song_metadata.title} - {'; '.join([a.name for a in song_metadata.artists])}")
|
||||
|
||||
preferences = Preferences()
|
||||
preferences.real_time_dl = real_time_dl
|
||||
@@ -300,90 +264,50 @@ class SpoLogin:
|
||||
|
||||
logger.info(f"Starting download for playlist: {playlist_json.get('name', 'Unknown')}")
|
||||
|
||||
for track_item_wrapper in playlist_json['tracks']['items']:
|
||||
track_info = track_item_wrapper.get('track')
|
||||
c_song_metadata = None # Initialize for each item
|
||||
|
||||
if not track_info:
|
||||
logger.warning(f"Skipping an item in playlist {playlist_json.get('name', 'Unknown Playlist')} as it does not appear to be a valid track object.")
|
||||
# Create a placeholder for this unidentifiable item
|
||||
c_song_metadata = {
|
||||
'name': 'Unknown Skipped Item',
|
||||
'ids': None,
|
||||
'error_type': 'InvalidItemStructure',
|
||||
'error_message': 'Playlist item was not a valid track object.'
|
||||
}
|
||||
song_metadata.append(c_song_metadata)
|
||||
playlist_tracks_data = playlist_json.get('tracks', {}).get('items', [])
|
||||
if not playlist_tracks_data:
|
||||
logger.warning(f"Playlist {link_playlist} has no tracks or could not be fetched.")
|
||||
# We can still proceed to create an empty playlist object for consistency
|
||||
|
||||
song_metadata_list = []
|
||||
for item in playlist_tracks_data:
|
||||
if not item or 'track' not in item or not item['track']:
|
||||
# Log a warning for items that are not valid tracks (e.g., local files, etc.)
|
||||
logger.warning(f"Skipping an item in playlist {link_playlist} as it does not appear to be a valid track object.")
|
||||
song_metadata_list.append({'error_type': 'invalid_track_object', 'error_message': 'Playlist item was not a valid track object.', 'name': 'Unknown Skipped Item', 'ids': None})
|
||||
continue
|
||||
|
||||
track_data = item['track']
|
||||
track_id = track_data.get('id')
|
||||
|
||||
if not track_id:
|
||||
logger.warning(f"Skipping an item in playlist {link_playlist} because it has no track ID.")
|
||||
song_metadata_list.append({'error_type': 'missing_track_id', 'error_message': 'Playlist item is missing a track ID.', 'name': track_data.get('name', 'Unknown Track without ID'), 'ids': None})
|
||||
continue
|
||||
|
||||
track_name_for_logs = track_info.get('name', 'Unknown Track')
|
||||
track_id_for_logs = track_info.get('id', 'Unknown ID') # Track's own ID if available
|
||||
external_urls = track_info.get('external_urls')
|
||||
|
||||
if not external_urls or not external_urls.get('spotify'):
|
||||
logger.warning(f"Track \"{track_name_for_logs}\" (ID: {track_id_for_logs}) in playlist {playlist_json.get('name', 'Unknown Playlist')} is not available on Spotify or has no URL.")
|
||||
c_song_metadata = {
|
||||
'name': track_name_for_logs,
|
||||
'ids': track_id_for_logs, # Use track's own ID if available, otherwise will be None
|
||||
'error_type': 'MissingTrackURL',
|
||||
'error_message': f"Track \"{track_name_for_logs}\" is not available on Spotify or has no URL."
|
||||
}
|
||||
else:
|
||||
track_spotify_url = external_urls['spotify']
|
||||
track_ids_from_url = get_ids(track_spotify_url) # This is the ID used for fetching with 'tracking'
|
||||
try:
|
||||
# Market check for each track is done within tracking()
|
||||
# Pass market. tracking() will raise MarketAvailabilityError if unavailable.
|
||||
fetched_metadata = tracking(track_ids_from_url, market=market)
|
||||
if fetched_metadata:
|
||||
c_song_metadata = fetched_metadata
|
||||
else:
|
||||
# tracking() returned None, but didn't raise MarketAvailabilityError. General fetch error.
|
||||
logger.warning(f"Could not retrieve full metadata for track {track_name_for_logs} (ID: {track_ids_from_url}, URL: {track_spotify_url}) in playlist {playlist_json.get('name', 'Unknown Playlist')}. API error or other issue.")
|
||||
c_song_metadata = {
|
||||
'name': track_name_for_logs,
|
||||
'ids': track_ids_from_url,
|
||||
'error_type': 'MetadataFetchError',
|
||||
'error_message': f"Failed to fetch full metadata for track {track_name_for_logs}."
|
||||
}
|
||||
except MarketAvailabilityError as e:
|
||||
logger.warning(f"Track {track_name_for_logs} (ID: {track_ids_from_url}, URL: {track_spotify_url}) in playlist {playlist_json.get('name', 'Unknown Playlist')} is not available in the specified market(s). Skipping. Error: {str(e)}")
|
||||
c_song_metadata = {
|
||||
'name': track_name_for_logs,
|
||||
'ids': track_ids_from_url,
|
||||
'error_type': 'MarketAvailabilityError',
|
||||
'error_message': str(e)
|
||||
}
|
||||
except Exception as e_tracking: # Catch any other unexpected error from tracking()
|
||||
logger.error(f"Unexpected error fetching metadata for track {track_name_for_logs} (ID: {track_ids_from_url}, URL: {track_spotify_url}): {str(e_tracking)}")
|
||||
c_song_metadata = {
|
||||
'name': track_name_for_logs,
|
||||
'ids': track_ids_from_url,
|
||||
'error_type': 'UnexpectedTrackingError',
|
||||
'error_message': f"Unexpected error fetching metadata: {str(e_tracking)}"
|
||||
}
|
||||
|
||||
if c_song_metadata: # Ensure something is appended
|
||||
song_metadata.append(c_song_metadata)
|
||||
else:
|
||||
# This case should ideally not be reached if logic above is complete
|
||||
logger.error(f"Logic error: c_song_metadata remained None for track {track_name_for_logs} in playlist {playlist_json.get('name', 'Unknown Playlist')}")
|
||||
song_metadata.append({
|
||||
'name': track_name_for_logs,
|
||||
'ids': track_id_for_logs or track_ids_from_url,
|
||||
'error_type': 'InternalLogicError',
|
||||
'error_message': 'Internal error processing playlist track metadata.'
|
||||
})
|
||||
|
||||
try:
|
||||
song_metadata = tracking(track_id, market=market)
|
||||
if song_metadata:
|
||||
song_metadata_list.append(song_metadata)
|
||||
else:
|
||||
# Create a placeholder for tracks that fail metadata fetching
|
||||
failed_track_info = {'error_type': 'metadata_fetch_failed', 'error_message': f"Failed to fetch metadata for track ID: {track_id}", 'name': track_data.get('name', f'Track ID {track_id}'), 'ids': track_id}
|
||||
song_metadata_list.append(failed_track_info)
|
||||
logger.warning(f"Could not retrieve metadata for track {track_id} in playlist {link_playlist}.")
|
||||
except MarketAvailabilityError as e:
|
||||
failed_track_info = {'error_type': 'market_availability_error', 'error_message': str(e), 'name': track_data.get('name', f'Track ID {track_id}'), 'ids': track_id}
|
||||
song_metadata_list.append(failed_track_info)
|
||||
logger.warning(str(e))
|
||||
|
||||
preferences = Preferences()
|
||||
preferences.real_time_dl = real_time_dl
|
||||
preferences.link = link_playlist
|
||||
preferences.song_metadata = song_metadata
|
||||
preferences.song_metadata = song_metadata_list
|
||||
preferences.quality_download = quality_download
|
||||
preferences.output_dir = output_dir
|
||||
preferences.ids = ids
|
||||
preferences.json_data = playlist_json
|
||||
preferences.playlist_tracks_json = playlist_tracks_data
|
||||
preferences.recursive_quality = recursive_quality
|
||||
preferences.recursive_download = recursive_download
|
||||
preferences.not_interface = not_interface
|
||||
@@ -403,7 +327,7 @@ class SpoLogin:
|
||||
preferences.bitrate = bitrate
|
||||
preferences.save_cover = save_cover
|
||||
preferences.market = market
|
||||
|
||||
|
||||
playlist = DW_PLAYLIST(preferences).dw()
|
||||
|
||||
return playlist
|
||||
@@ -445,7 +369,7 @@ class SpoLogin:
|
||||
if episode_metadata is None:
|
||||
raise Exception(f"Could not process episode metadata for {link_episode}. It might not be available in the specified market(s) or an API error occurred.")
|
||||
|
||||
logger.info(f"Starting download for episode: {episode_metadata.get('name', 'Unknown')} - {episode_metadata.get('show', 'Unknown')}")
|
||||
logger.info(f"Starting download for episode: {episode_metadata.title} - {episode_metadata.album.title}")
|
||||
|
||||
preferences = Preferences()
|
||||
preferences.real_time_dl = real_time_dl
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
from deezspot.easy_spoty import Spo
|
||||
from datetime import datetime
|
||||
from deezspot.libutils.utils import convert_to_date
|
||||
import traceback
|
||||
from deezspot.libutils.logging_utils import logger
|
||||
from deezspot.exceptions import MarketAvailabilityError
|
||||
from typing import List, Optional, Dict, Any
|
||||
|
||||
from deezspot.models.callback.album import albumObject, artistAlbumObject, trackAlbumObject as CbTrackAlbumObject, artistTrackAlbumObject
|
||||
from deezspot.models.callback.artist import artistObject
|
||||
from deezspot.models.callback.common import IDs
|
||||
from deezspot.models.callback.playlist import playlistObject, trackPlaylistObject, albumTrackPlaylistObject, artistTrackPlaylistObject, artistAlbumTrackPlaylistObject
|
||||
from deezspot.models.callback.track import trackObject, artistTrackObject, albumTrackObject, artistAlbumTrackObject
|
||||
from deezspot.models.callback.user import userObject
|
||||
|
||||
|
||||
def _check_market_availability(item_name: str, item_type: str, api_available_markets: list[str] | None, user_markets: list[str] | None):
|
||||
"""Checks if an item is available in any of the user-specified markets."""
|
||||
@@ -15,338 +22,256 @@ def _check_market_availability(item_name: str, item_type: str, api_available_mar
|
||||
markets_str = ", ".join(user_markets)
|
||||
raise MarketAvailabilityError(f"{item_type} '{item_name}' not available in provided market(s): {markets_str}")
|
||||
elif user_markets and api_available_markets is None:
|
||||
# Log a warning if user specified markets, but API response doesn't include 'available_markets'
|
||||
# This might indicate the item is available in all markets or API doesn't provide this info for this item type.
|
||||
# For now, we proceed without raising an error, as we cannot confirm it's "not available".
|
||||
logger.warning(
|
||||
f"Market availability check for {item_type} '{item_name}' skipped: "
|
||||
"API response did not include 'available_markets' field. Assuming availability."
|
||||
)
|
||||
|
||||
def _get_best_image_urls(images_list):
|
||||
urls = {'image': '', 'image2': '', 'image3': ''}
|
||||
if not images_list or not isinstance(images_list, list):
|
||||
return urls
|
||||
def _parse_release_date(date_str: Optional[str], precision: Optional[str]) -> Dict[str, Any]:
|
||||
if not date_str:
|
||||
return {}
|
||||
|
||||
parts = date_str.split('-')
|
||||
data = {}
|
||||
|
||||
if len(parts) >= 1 and parts[0]:
|
||||
data['year'] = int(parts[0])
|
||||
if precision in ['month', 'day'] and len(parts) >= 2 and parts[1]:
|
||||
data['month'] = int(parts[1])
|
||||
if precision == 'day' and len(parts) >= 3 and parts[2]:
|
||||
data['day'] = int(parts[2])
|
||||
|
||||
return data
|
||||
|
||||
# Sort images by area (height * width) in descending order
|
||||
# Handle cases where height or width might be missing
|
||||
sorted_images = sorted(
|
||||
images_list,
|
||||
key=lambda img: img.get('height', 0) * img.get('width', 0),
|
||||
reverse=True
|
||||
def _json_to_ids(item_json: dict) -> IDs:
|
||||
external_ids = item_json.get('external_ids', {})
|
||||
return IDs(
|
||||
spotify=item_json.get('id'),
|
||||
isrc=external_ids.get('isrc'),
|
||||
upc=external_ids.get('upc')
|
||||
)
|
||||
|
||||
if len(sorted_images) > 0:
|
||||
urls['image'] = sorted_images[0].get('url', '')
|
||||
if len(sorted_images) > 1:
|
||||
urls['image2'] = sorted_images[1].get('url', '') # Second largest or same if only one size
|
||||
if len(sorted_images) > 2:
|
||||
urls['image3'] = sorted_images[2].get('url', '') # Third largest
|
||||
|
||||
return urls
|
||||
def _json_to_artist_track_object(artist_json: dict) -> artistTrackObject:
|
||||
return artistTrackObject(
|
||||
name=artist_json.get('name', ''),
|
||||
ids=_json_to_ids(artist_json)
|
||||
)
|
||||
|
||||
def tracking(ids, album_data_for_track=None, market: list[str] | None = None):
|
||||
datas = {}
|
||||
def _json_to_artist_album_track_object(artist_json: dict) -> artistAlbumTrackObject:
|
||||
return artistAlbumTrackObject(
|
||||
name=artist_json.get('name', ''),
|
||||
ids=_json_to_ids(artist_json)
|
||||
)
|
||||
|
||||
def _json_to_album_track_object(album_json: dict) -> albumTrackObject:
|
||||
return albumTrackObject(
|
||||
album_type=album_json.get('album_type', 'album'),
|
||||
title=album_json.get('name', ''),
|
||||
release_date=_parse_release_date(album_json.get('release_date'), album_json.get('release_date_precision')),
|
||||
total_tracks=album_json.get('total_tracks', 0),
|
||||
genres=album_json.get('genres', []),
|
||||
images=album_json.get('images', []),
|
||||
ids=_json_to_ids(album_json),
|
||||
artists=[_json_to_artist_album_track_object(artist) for artist in album_json.get('artists', [])]
|
||||
)
|
||||
|
||||
def tracking(ids, album_data_for_track=None, market: list[str] | None = None) -> Optional[trackObject]:
|
||||
try:
|
||||
json_track = Spo.get_track(ids)
|
||||
if not json_track:
|
||||
logger.error(f"Failed to get track details for ID: {ids} from Spotify API.")
|
||||
return None
|
||||
|
||||
# Perform market availability check for the track
|
||||
track_name_for_check = json_track.get('name', f'Track ID {ids}')
|
||||
api_track_markets = json_track.get('available_markets')
|
||||
_check_market_availability(track_name_for_check, "Track", api_track_markets, market)
|
||||
|
||||
# Album details section
|
||||
# Use provided album_data_for_track if available (from tracking_album context)
|
||||
# Otherwise, fetch from track's album info or make a new API call for more details
|
||||
album_to_process = None
|
||||
fetch_full_album_details = False
|
||||
|
||||
if album_data_for_track:
|
||||
album_to_process = album_data_for_track
|
||||
elif json_track.get('album'):
|
||||
album_to_process = json_track.get('album')
|
||||
# We might want fuller album details (like label, genres, upc, copyrights)
|
||||
# not present in track's nested album object.
|
||||
fetch_full_album_details = True
|
||||
album_id = json_track.get('album', {}).get('id')
|
||||
if album_id:
|
||||
album_to_process = Spo.get_album(album_id)
|
||||
if not album_to_process:
|
||||
album_to_process = json_track.get('album')
|
||||
|
||||
album_for_track = _json_to_album_track_object(album_to_process) if album_to_process else albumTrackObject()
|
||||
|
||||
if fetch_full_album_details and album_to_process and album_to_process.get('id'):
|
||||
full_album_json = Spo.get_album(album_to_process.get('id'))
|
||||
if full_album_json:
|
||||
album_to_process = full_album_json # Prioritize full album details
|
||||
|
||||
if album_to_process:
|
||||
image_urls = _get_best_image_urls(album_to_process.get('images', []))
|
||||
datas.update(image_urls)
|
||||
|
||||
datas['genre'] = "; ".join(album_to_process.get('genres', []))
|
||||
|
||||
album_artists_data = album_to_process.get('artists', [])
|
||||
ar_album_names = [artist.get('name', '') for artist in album_artists_data if artist.get('name')]
|
||||
datas['ar_album'] = "; ".join(filter(None, ar_album_names)) or 'Unknown Artist'
|
||||
|
||||
datas['album'] = album_to_process.get('name', 'Unknown Album')
|
||||
datas['label'] = album_to_process.get('label', '') # Often in full album, not track's album obj
|
||||
datas['album_type'] = album_to_process.get('album_type', 'unknown')
|
||||
|
||||
copyrights_data = album_to_process.get('copyrights', [])
|
||||
datas['copyright'] = copyrights_data[0].get('text', '') if copyrights_data else ''
|
||||
|
||||
album_external_ids = album_to_process.get('external_ids', {})
|
||||
datas['upc'] = album_external_ids.get('upc', '')
|
||||
|
||||
datas['nb_tracks'] = album_to_process.get('total_tracks', 0)
|
||||
# Release date from album_to_process is likely more definitive
|
||||
datas['year'] = convert_to_date(album_to_process.get('release_date', ''))
|
||||
datas['release_date_precision'] = album_to_process.get('release_date_precision', 'unknown')
|
||||
else: # Fallback if no album_to_process
|
||||
datas.update(_get_best_image_urls([]))
|
||||
datas['genre'] = ''
|
||||
datas['ar_album'] = 'Unknown Artist'
|
||||
datas['album'] = json_track.get('album', {}).get('name', 'Unknown Album') # Basic fallback
|
||||
datas['label'] = ''
|
||||
datas['album_type'] = json_track.get('album', {}).get('album_type', 'unknown')
|
||||
datas['copyright'] = ''
|
||||
datas['upc'] = ''
|
||||
datas['nb_tracks'] = json_track.get('album', {}).get('total_tracks', 0)
|
||||
datas['year'] = convert_to_date(json_track.get('album', {}).get('release_date', ''))
|
||||
datas['release_date_precision'] = json_track.get('album', {}).get('release_date_precision', 'unknown')
|
||||
|
||||
|
||||
# Track specific details
|
||||
datas['music'] = json_track.get('name', 'Unknown Track')
|
||||
|
||||
track_artists_data = json_track.get('artists', [])
|
||||
track_artist_names = [artist.get('name', '') for artist in track_artists_data if artist.get('name')]
|
||||
datas['artist'] = "; ".join(filter(None, track_artist_names)) or 'Unknown Artist'
|
||||
|
||||
datas['tracknum'] = json_track.get('track_number', 0)
|
||||
datas['discnum'] = json_track.get('disc_number', 0)
|
||||
|
||||
# If year details were not set from a more complete album object, use track's album info
|
||||
if not datas.get('year') and json_track.get('album'):
|
||||
datas['year'] = convert_to_date(json_track.get('album', {}).get('release_date', ''))
|
||||
datas['release_date_precision'] = json_track.get('album', {}).get('release_date_precision', 'unknown')
|
||||
|
||||
datas['duration'] = json_track.get('duration_ms', 0) // 1000
|
||||
|
||||
track_external_ids = json_track.get('external_ids', {})
|
||||
datas['isrc'] = track_external_ids.get('isrc', '')
|
||||
|
||||
datas['explicit'] = json_track.get('explicit', False)
|
||||
datas['popularity'] = json_track.get('popularity', 0)
|
||||
|
||||
# Placeholder for tags not directly from this API response but might be expected by tagger
|
||||
datas['bpm'] = datas.get('bpm', 'Unknown') # Not available here
|
||||
datas['gain'] = datas.get('gain', 'Unknown') # Not available here
|
||||
datas['lyric'] = datas.get('lyric', '') # Not available here
|
||||
datas['author'] = datas.get('author', '') # Not available here (lyricist)
|
||||
datas['composer'] = datas.get('composer', '') # Not available here
|
||||
# copyright is handled by album section
|
||||
datas['lyricist'] = datas.get('lyricist', '') # Same as author, not here
|
||||
datas['version'] = datas.get('version', '') # Not typically here
|
||||
|
||||
datas['ids'] = ids
|
||||
track_obj = trackObject(
|
||||
title=json_track.get('name', ''),
|
||||
disc_number=json_track.get('disc_number', 1),
|
||||
track_number=json_track.get('track_number', 1),
|
||||
duration_ms=json_track.get('duration_ms', 0),
|
||||
explicit=json_track.get('explicit', False),
|
||||
genres=album_for_track.genres,
|
||||
album=album_for_track,
|
||||
artists=[_json_to_artist_track_object(artist) for artist in json_track.get('artists', [])],
|
||||
ids=_json_to_ids(json_track)
|
||||
)
|
||||
logger.debug(f"Successfully tracked metadata for track {ids}")
|
||||
return track_obj
|
||||
|
||||
except MarketAvailabilityError: # Re-raise to be caught by the calling download method
|
||||
except MarketAvailabilityError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to track metadata for track {ids}: {str(e)}")
|
||||
logger.debug(traceback.format_exc())
|
||||
return None
|
||||
|
||||
return datas
|
||||
def _json_to_artist_album_object(artist_json: dict) -> artistAlbumObject:
|
||||
return artistAlbumObject(
|
||||
name=artist_json.get('name', ''),
|
||||
ids=_json_to_ids(artist_json)
|
||||
)
|
||||
|
||||
def tracking_album(album_json, market: list[str] | None = None):
|
||||
def _json_to_track_album_object(track_json: dict) -> CbTrackAlbumObject:
|
||||
return CbTrackAlbumObject(
|
||||
title=track_json.get('name', ''),
|
||||
disc_number=track_json.get('disc_number', 1),
|
||||
track_number=track_json.get('track_number', 1),
|
||||
duration_ms=track_json.get('duration_ms', 0),
|
||||
explicit=track_json.get('explicit', False),
|
||||
ids=_json_to_ids(track_json),
|
||||
artists=[artistTrackAlbumObject(name=a.get('name'), ids=_json_to_ids(a)) for a in track_json.get('artists', [])]
|
||||
)
|
||||
|
||||
def tracking_album(album_json, market: list[str] | None = None) -> Optional[albumObject]:
|
||||
if not album_json:
|
||||
logger.error("tracking_album received None or empty album_json.")
|
||||
return None
|
||||
|
||||
song_metadata = {}
|
||||
try:
|
||||
# Perform market availability check for the album itself
|
||||
album_name_for_check = album_json.get('name', f"Album ID {album_json.get('id', 'Unknown')}")
|
||||
api_album_markets = album_json.get('available_markets')
|
||||
_check_market_availability(album_name_for_check, "Album", api_album_markets, market)
|
||||
|
||||
initial_list_fields = {
|
||||
"music": [], "artist": [], "tracknum": [], "discnum": [],
|
||||
"duration": [], "isrc": [], "ids": [], "explicit_list": [], "popularity_list": []
|
||||
# "bpm": [], "gain": [] are usually unknown from this endpoint for tracks
|
||||
}
|
||||
song_metadata.update(initial_list_fields)
|
||||
|
||||
image_urls = _get_best_image_urls(album_json.get('images', []))
|
||||
song_metadata.update(image_urls)
|
||||
|
||||
song_metadata['album'] = album_json.get('name', 'Unknown Album')
|
||||
song_metadata['label'] = album_json.get('label', '')
|
||||
song_metadata['year'] = convert_to_date(album_json.get('release_date', ''))
|
||||
song_metadata['release_date_precision'] = album_json.get('release_date_precision', 'unknown')
|
||||
song_metadata['nb_tracks'] = album_json.get('total_tracks', 0)
|
||||
song_metadata['genre'] = "; ".join(album_json.get('genres', []))
|
||||
song_metadata['album_type'] = album_json.get('album_type', 'unknown')
|
||||
song_metadata['popularity'] = album_json.get('popularity', 0)
|
||||
|
||||
album_artists_data = album_json.get('artists', [])
|
||||
ar_album_names = [artist.get('name', '') for artist in album_artists_data if artist.get('name')]
|
||||
song_metadata['ar_album'] = "; ".join(filter(None, ar_album_names)) or 'Unknown Artist'
|
||||
|
||||
album_external_ids = album_json.get('external_ids', {})
|
||||
song_metadata['upc'] = album_external_ids.get('upc', '')
|
||||
|
||||
copyrights_data = album_json.get('copyrights', [])
|
||||
song_metadata['copyright'] = copyrights_data[0].get('text', '') if copyrights_data else ''
|
||||
album_artists = [_json_to_artist_album_object(a) for a in album_json.get('artists', [])]
|
||||
|
||||
# Add other common flat metadata keys with defaults if not directly from album_json
|
||||
song_metadata['bpm'] = 'Unknown'
|
||||
song_metadata['gain'] = 'Unknown'
|
||||
song_metadata['lyric'] = ''
|
||||
song_metadata['author'] = ''
|
||||
song_metadata['composer'] = ''
|
||||
song_metadata['lyricist'] = ''
|
||||
song_metadata['version'] = ''
|
||||
album_tracks = []
|
||||
simplified_tracks = album_json.get('tracks', {}).get('items', [])
|
||||
track_ids = [t['id'] for t in simplified_tracks if t and t.get('id')]
|
||||
|
||||
full_tracks_data = []
|
||||
if track_ids:
|
||||
# Batch fetch full track objects. The get_tracks method should handle chunking if necessary.
|
||||
full_tracks_data = Spo.get_tracks(track_ids, market=','.join(market) if market else None)
|
||||
|
||||
track_items_to_process = []
|
||||
if full_tracks_data and full_tracks_data.get('tracks'):
|
||||
track_items_to_process = full_tracks_data['tracks']
|
||||
else: # Fallback to simplified if batch fetch fails
|
||||
track_items_to_process = simplified_tracks
|
||||
|
||||
tracks_data = album_json.get('tracks', {}).get('items', [])
|
||||
for track_item in tracks_data:
|
||||
if not track_item: continue # Skip if track_item is None
|
||||
c_ids = track_item.get('id')
|
||||
if not c_ids: # If track has no ID, try to get some basic info directly
|
||||
song_metadata['music'].append(track_item.get('name', 'Unknown Track'))
|
||||
track_artists_data = track_item.get('artists', [])
|
||||
track_artist_names = [artist.get('name', '') for artist in track_artists_data if artist.get('name')]
|
||||
song_metadata['artist'].append("; ".join(filter(None, track_artist_names)) or 'Unknown Artist')
|
||||
song_metadata['tracknum'].append(track_item.get('track_number', 0))
|
||||
song_metadata['discnum'].append(track_item.get('disc_number', 0))
|
||||
song_metadata['duration'].append(track_item.get('duration_ms', 0) // 1000)
|
||||
song_metadata['isrc'].append(track_item.get('external_ids', {}).get('isrc', ''))
|
||||
song_metadata['ids'].append('N/A')
|
||||
song_metadata['explicit_list'].append(track_item.get('explicit', False))
|
||||
song_metadata['popularity_list'].append(track_item.get('popularity', 0))
|
||||
for track_item in track_items_to_process:
|
||||
if not track_item or not track_item.get('id'):
|
||||
continue
|
||||
|
||||
# Pass the main album_json as album_data_for_track to avoid refetching it in tracking()
|
||||
# Also pass the market parameter
|
||||
track_details = tracking(c_ids, album_data_for_track=album_json, market=market)
|
||||
|
||||
if track_details:
|
||||
song_metadata['music'].append(track_details.get('music', 'Unknown Track'))
|
||||
song_metadata['artist'].append(track_details.get('artist', 'Unknown Artist'))
|
||||
song_metadata['tracknum'].append(track_details.get('tracknum', 0))
|
||||
song_metadata['discnum'].append(track_details.get('discnum', 0))
|
||||
# BPM and Gain are generally not per-track from this endpoint
|
||||
# song_metadata['bpm'].append(track_details.get('bpm', 'Unknown'))
|
||||
song_metadata['duration'].append(track_details.get('duration', 0))
|
||||
song_metadata['isrc'].append(track_details.get('isrc', ''))
|
||||
song_metadata['ids'].append(c_ids)
|
||||
song_metadata['explicit_list'].append(track_details.get('explicit', False))
|
||||
# popularity_list for track specific popularity if needed, or use album popularity
|
||||
# song_metadata['popularity_list'].append(track_details.get('popularity',0))
|
||||
|
||||
else: # Fallback if tracking(c_ids) failed
|
||||
logger.warning(f"Could not retrieve full metadata for track ID {c_ids} in album {album_json.get('id', 'N/A')}. Using minimal data.")
|
||||
song_metadata['music'].append(track_item.get('name', 'Unknown Track'))
|
||||
track_artists_data = track_item.get('artists', [])
|
||||
track_artist_names = [artist.get('name', '') for artist in track_artists_data if artist.get('name')]
|
||||
song_metadata['artist'].append("; ".join(filter(None, track_artist_names)) or 'Unknown Artist')
|
||||
song_metadata['tracknum'].append(track_item.get('track_number', 0))
|
||||
song_metadata['discnum'].append(track_item.get('disc_number', 0))
|
||||
song_metadata['duration'].append(track_item.get('duration_ms', 0) // 1000)
|
||||
song_metadata['isrc'].append(track_item.get('external_ids', {}).get('isrc', ''))
|
||||
song_metadata['ids'].append(c_ids)
|
||||
song_metadata['explicit_list'].append(track_item.get('explicit', False))
|
||||
# song_metadata['popularity_list'].append(track_item.get('popularity',0))
|
||||
# Simplified track object from album endpoint is enough for trackAlbumObject
|
||||
album_tracks.append(_json_to_track_album_object(track_item))
|
||||
|
||||
album_obj = albumObject(
|
||||
album_type=album_json.get('album_type'),
|
||||
title=album_json.get('name'),
|
||||
release_date=_parse_release_date(album_json.get('release_date'), album_json.get('release_date_precision')),
|
||||
total_tracks=album_json.get('total_tracks'),
|
||||
genres=album_json.get('genres', []),
|
||||
images=album_json.get('images', []),
|
||||
copyrights=album_json.get('copyrights', []),
|
||||
ids=_json_to_ids(album_json),
|
||||
tracks=album_tracks,
|
||||
artists=album_artists
|
||||
)
|
||||
|
||||
logger.debug(f"Successfully tracked metadata for album {album_json.get('id', 'N/A')}")
|
||||
return album_obj
|
||||
|
||||
except MarketAvailabilityError: # Re-raise
|
||||
except MarketAvailabilityError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to track album metadata for album ID {album_json.get('id', 'N/A') if album_json else 'N/A'}: {str(e)}")
|
||||
logger.debug(traceback.format_exc())
|
||||
return None
|
||||
|
||||
return song_metadata
|
||||
|
||||
def tracking_episode(ids, market: list[str] | None = None):
|
||||
datas = {}
|
||||
def tracking_episode(ids, market: list[str] | None = None) -> Optional[trackObject]:
|
||||
try:
|
||||
json_episode = Spo.get_episode(ids)
|
||||
if not json_episode:
|
||||
logger.error(f"Failed to get episode details for ID: {ids} from Spotify API.")
|
||||
return None
|
||||
|
||||
# Perform market availability check for the episode
|
||||
episode_name_for_check = json_episode.get('name', f'Episode ID {ids}')
|
||||
api_episode_markets = json_episode.get('available_markets')
|
||||
_check_market_availability(episode_name_for_check, "Episode", api_episode_markets, market)
|
||||
|
||||
image_urls = _get_best_image_urls(json_episode.get('images', []))
|
||||
datas.update(image_urls)
|
||||
|
||||
datas['audio_preview_url'] = json_episode.get('audio_preview_url', '')
|
||||
datas['description'] = json_episode.get('description', '')
|
||||
datas['duration'] = json_episode.get('duration_ms', 0) // 1000
|
||||
datas['explicit'] = json_episode.get('explicit', False)
|
||||
datas['external_urls_spotify'] = json_episode.get('external_urls', {}).get('spotify', '')
|
||||
datas['href'] = json_episode.get('href', '')
|
||||
datas['html_description'] = json_episode.get('html_description', '')
|
||||
datas['id'] = json_episode.get('id', '') # Episode's own ID
|
||||
|
||||
datas['is_externally_hosted'] = json_episode.get('is_externally_hosted', False)
|
||||
datas['is_playable'] = json_episode.get('is_playable', False)
|
||||
datas['language'] = json_episode.get('language', '') # Deprecated, use languages
|
||||
datas['languages'] = "; ".join(json_episode.get('languages', []))
|
||||
datas['music'] = json_episode.get('name', 'Unknown Episode') # Use 'music' for consistency with track naming
|
||||
datas['name'] = json_episode.get('name', 'Unknown Episode') # Keep 'name' as well if needed by other parts
|
||||
|
||||
datas['release_date'] = convert_to_date(json_episode.get('release_date', ''))
|
||||
datas['release_date_precision'] = json_episode.get('release_date_precision', 'unknown')
|
||||
|
||||
show_data = json_episode.get('show', {})
|
||||
datas['show_name'] = show_data.get('name', 'Unknown Show')
|
||||
datas['publisher'] = show_data.get('publisher', 'Unknown Publisher')
|
||||
datas['show_description'] = show_data.get('description', '')
|
||||
datas['show_explicit'] = show_data.get('explicit', False)
|
||||
datas['show_total_episodes'] = show_data.get('total_episodes', 0)
|
||||
datas['show_media_type'] = show_data.get('media_type', 'unknown') # e.g. 'audio'
|
||||
|
||||
# For tagger compatibility, map some show data to common track/album fields
|
||||
datas['artist'] = datas['publisher'] # Publisher as artist for episodes
|
||||
datas['album'] = datas['show_name'] # Show name as album for episodes
|
||||
datas['genre'] = "; ".join(show_data.get('genres', [])) # If shows have genres
|
||||
datas['copyright'] = copyrights_data[0].get('text', '') if (copyrights_data := show_data.get('copyrights', [])) else ''
|
||||
|
||||
|
||||
# Placeholder for tags not directly from this API response but might be expected by tagger
|
||||
datas['tracknum'] = 1 # Default for single episode
|
||||
datas['discnum'] = 1 # Default for single episode
|
||||
datas['ar_album'] = datas['publisher']
|
||||
datas['label'] = datas['publisher']
|
||||
datas['bpm'] = 'Unknown'
|
||||
datas['gain'] = 'Unknown'
|
||||
datas['isrc'] = ''
|
||||
datas['upc'] = ''
|
||||
datas['lyric'] = ''
|
||||
datas['author'] = ''
|
||||
datas['composer'] = ''
|
||||
datas['lyricist'] = ''
|
||||
datas['version'] = ''
|
||||
|
||||
datas['ids'] = ids # The episode's own ID passed to the function
|
||||
album_for_episode = albumTrackObject(
|
||||
album_type='show',
|
||||
title=show_data.get('name', 'Unknown Show'),
|
||||
total_tracks=show_data.get('total_episodes', 0),
|
||||
genres=show_data.get('genres', []),
|
||||
images=json_episode.get('images', []),
|
||||
ids=IDs(spotify=show_data.get('id')),
|
||||
artists=[artistTrackAlbumObject(name=show_data.get('publisher', ''))]
|
||||
)
|
||||
|
||||
episode_as_track = trackObject(
|
||||
title=json_episode.get('name', 'Unknown Episode'),
|
||||
duration_ms=json_episode.get('duration_ms', 0),
|
||||
explicit=json_episode.get('explicit', False),
|
||||
album=album_for_episode,
|
||||
artists=[artistTrackObject(name=show_data.get('publisher', ''))],
|
||||
ids=_json_to_ids(json_episode)
|
||||
)
|
||||
|
||||
logger.debug(f"Successfully tracked metadata for episode {ids}")
|
||||
return episode_as_track
|
||||
|
||||
except MarketAvailabilityError: # Re-raise
|
||||
except MarketAvailabilityError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to track episode metadata for ID {ids}: {str(e)}")
|
||||
logger.debug(traceback.format_exc())
|
||||
return None
|
||||
|
||||
return datas
|
||||
def json_to_artist_album_track_playlist_object(artist_json: dict) -> artistAlbumTrackPlaylistObject:
|
||||
"""Converts a JSON dict to an artistAlbumTrackPlaylistObject."""
|
||||
return artistAlbumTrackPlaylistObject(
|
||||
name=artist_json.get('name', ''),
|
||||
ids=_json_to_ids(artist_json)
|
||||
)
|
||||
|
||||
def json_to_artist_track_playlist_object(artist_json: dict) -> artistTrackPlaylistObject:
|
||||
"""Converts a JSON dict to an artistTrackPlaylistObject."""
|
||||
return artistTrackPlaylistObject(
|
||||
name=artist_json.get('name', ''),
|
||||
ids=_json_to_ids(artist_json)
|
||||
)
|
||||
|
||||
def json_to_album_track_playlist_object(album_json: dict) -> albumTrackPlaylistObject:
|
||||
"""Converts a JSON dict to an albumTrackPlaylistObject."""
|
||||
return albumTrackPlaylistObject(
|
||||
album_type=album_json.get('album_type', ''),
|
||||
title=album_json.get('name', ''),
|
||||
total_tracks=album_json.get('total_tracks', 0),
|
||||
release_date=_parse_release_date(album_json.get('release_date'), album_json.get('release_date_precision')),
|
||||
images=album_json.get('images', []),
|
||||
ids=_json_to_ids(album_json),
|
||||
artists=[json_to_artist_album_track_playlist_object(a) for a in album_json.get('artists', [])]
|
||||
)
|
||||
|
||||
def json_to_track_playlist_object(track_json: dict) -> Optional[trackPlaylistObject]:
|
||||
"""Converts a JSON dict from a playlist item to a trackPlaylistObject."""
|
||||
if not track_json:
|
||||
return None
|
||||
album_data = track_json.get('album', {})
|
||||
return trackPlaylistObject(
|
||||
title=track_json.get('name', ''),
|
||||
disc_number=track_json.get('disc_number', 1),
|
||||
track_number=track_json.get('track_number', 1),
|
||||
duration_ms=track_json.get('duration_ms', 0),
|
||||
ids=_json_to_ids(track_json),
|
||||
album=json_to_album_track_playlist_object(album_data),
|
||||
artists=[json_to_artist_track_playlist_object(a) for a in track_json.get('artists', [])]
|
||||
)
|
||||
Reference in New Issue
Block a user