from apollo_utils.core.constants.dsp import DSP from apollo_utils.core.constants.market import Market from apollo_utils.service.schemas.fields.datetime import DateStringField, DateTimeStringField from apollo_utils.service.schemas.fields.enum import EnumListField from apollo_utils.service.schemas.fields.list import SplitUniqueListField from apollo_utils.service.schemas.fields.market import MarketField from apollo_utils.service.schemas.fields.playlists import PlaylistIdField from apollo_utils.service.schemas.fields.unix_epoch import UnixEpochCheckAndReplaceDateStringField from apollo_utils.service.schemas.mixins.isrc import SourceTrackISRCLowerMixin from apollo_utils.service.schemas.mixins.search import SearchNoneLowerMixin from datetime import datetime from marshmallow import fields, post_load, validate from server.constants.distributors import DISTRIBUTORS from server.constants.playlists.v0.misc import PlaylistTracklistOrder from server.constants.tracks import SPOTIFY_TRACK_URI_PREFIX from server.schemas.base.pagination import PaginationMixin, PaginationResultMixin from server.schemas.base.schemas import RequestSchema, Schema from server.schemas.images.core_image_service import CoreImageServiceImageSizeMixin, CoreImageServiceImageUrlField from server.schemas.playlists.common.blue_dot import IsEntryBlueDotMixin from server.schemas.playlists.spotify.dates import URIPlaylistIdMixin from server.utils.common import get_inner_value class TracklistItemBase(Schema): class TrackArtist(Schema): name = fields.String() uri = fields.String(attribute="url") trackUri = fields.Function( lambda item: get_inner_value(item, "track_id", convert_func=lambda i: f"{SPOTIFY_TRACK_URI_PREFIX}{i}") ) name = fields.String(attribute="track.name") artistName = fields.Function( lambda item: ",".join( i["name"] for i in get_inner_value(item, "track", "artists", default_value=[]) if i.get("name") ) ) isSony = fields.Boolean(attribute="track.is_sony") isrc = fields.String() albumImageUrl = fields.String(attribute="track.image_url") albumReleaseDate = DateStringField(attribute="track.album.release_date") earliestAdded = DateTimeStringField(attribute="earliest_position_date_time") artists = fields.List(fields.Nested(TrackArtist), attribute="track.artists") position = fields.Integer(attribute="current") danceability = fields.Float(attribute="track.audio_features.danceability", default=None) energy = fields.Float(attribute="track.audio_features.energy", default=None) audioKey = fields.Integer(attribute="track.audio_features.key", default=None) loudness = fields.Float(attribute="track.audio_features.loudness", default=None) audioMode = fields.Integer(attribute="track.audio_features.mode", default=None) speechiness = fields.Float(attribute="track.audio_features.speechiness", default=None) acousticness = fields.Float(attribute="track.audio_features.acousticness", default=None) instrumentalness = fields.Float(attribute="track.audio_features.instrumentalness", default=None) liveness = fields.Float(attribute="track.audio_features.liveness", default=None) valence = fields.Float(attribute="track.audio_features.valence", default=None) tempo = fields.Float(attribute="track.audio_features.tempo", default=None) popularity = fields.Integer(attribute="track.track_popularity", default=None) class V1PlaylistsSpotifyTracks: class Request(RequestSchema): playlist_id = PlaylistIdField( required=True, data_key="playlistUri", dsp=DSP.SPOTIFY, from_uri=True, validate=validate.Length(max=50) ) country_code = MarketField( data_key="region", missing=None, validate=validate.Length(min=2, max=10), gl=Market.WORLDWIDE ) distributors = EnumListField(DISTRIBUTORS, unify=True, missing=[DISTRIBUTORS.SME], as_enum=True) class Response(Schema): class TracklistItem(TracklistItemBase): addedDate = DateTimeStringField(attribute="last_added_date_time") positionChange = fields.Integer(attribute="trend") positionChangeDate = DateTimeStringField(attribute="change_position_date_time") isEntry = fields.Boolean(attribute="is_entry") distributed_by = fields.String(attribute="track.distributed_by", default=None) globalStreams = fields.Function( lambda i, c: get_inner_value(i, "streaming_info", Market.WORLDWIDE, "7", "isrc_in_playlist") ) marketStreams = fields.Function( lambda i, c: get_inner_value(i, "streaming_info", c["country_code"], "7", "isrc_in_playlist") ) tracks = fields.List(fields.Nested(TracklistItem)) class V1PlaylistsSpotifyTracksHistory: class Request(RequestSchema): playlist_id = PlaylistIdField( required=True, data_key="playlistUri", dsp=DSP.SPOTIFY, from_uri=True, validate=validate.Length(max=50) ) date = fields.Date(required=True) country_code = MarketField( data_key="region", missing=None, validate=validate.Length(min=2, max=10), gl=Market.WORLDWIDE ) distributors = EnumListField(DISTRIBUTORS, unify=True, missing=[DISTRIBUTORS.SME], as_enum=True) class Response(Schema): class TracklistItem(TracklistItemBase): added = DateTimeStringField(attribute="last_added_date_time") albumName = fields.String(attribute="track.album.name") distributed_by = fields.String(attribute="track.distributed_by", default=None) globalStreams = fields.Function( lambda i, c: get_inner_value(i, "streaming_info", Market.WORLDWIDE, "1", "isrc_in_playlist") ) marketStreams = fields.Function( lambda i, c: get_inner_value(i, "streaming_info", c["country_code"], "1", "isrc_in_playlist") ) items = fields.List(fields.Nested(TracklistItem)) class V1PlaylistsSpotifyTracklist: class Request( RequestSchema, SearchNoneLowerMixin, SourceTrackISRCLowerMixin, CoreImageServiceImageSizeMixin, PaginationMixin, ): playlist_id = PlaylistIdField(required=True, dsp=DSP.SPOTIFY, validate=validate.Length(max=50)) country_code = MarketField( data_key="market", missing=Market.WORLDWIDE, validate=validate.Length(min=2, max=10), gl=Market.WORLDWIDE ) order_by = SplitUniqueListField( fields.String(validate=validate.OneOf(PlaylistTracklistOrder.ALL)), default=[PlaylistTracklistOrder.ADDED_DATE, PlaylistTracklistOrder.CURR_POSITION], missing=[PlaylistTracklistOrder.ADDED_DATE, PlaylistTracklistOrder.CURR_POSITION], split=False, ) @post_load def reformat_search(self, data, *args, **kwargs): search = data.get("search") if search: data["search"] = search.replace(",", " ").split() if search.find(",") > 0 else [search] return data class Response(Schema, PaginationResultMixin): class TrackItem(Schema, IsEntryBlueDotMixin): track_id = fields.String(attribute="track_id", default=None) current_position = fields.Integer(attribute="current", default=None) added_date = UnixEpochCheckAndReplaceDateStringField( attribute="earliest_position_date_time", truncate_str=True ) name = fields.String(attribute="track.name") isrc = fields.String(default=None) album_id = fields.String(attribute="track.album.album_id") artists = fields.Function( lambda item: [ i["name"] for i in get_inner_value(item, "track", "artists", default_value=[]) if i.get("name") ] ) image_url = CoreImageServiceImageUrlField(attribute="track.image_url", default=None) previous_position = fields.Integer(attribute="previous", default=None) trend = fields.Integer(attribute="trend", default=None) position_change_date = DateStringField( attribute="change_position_date_time", truncate_str=True, default=None ) is_new = fields.Boolean(attribute="is_entry") update_date = fields.Function(lambda i: datetime.utcnow().date().isoformat()) items = fields.List(fields.Nested(TrackItem)) class V1PlaylistsSpotifyAnalyze: class Request(RequestSchema, URIPlaylistIdMixin): pass class Response(Schema): id = PlaylistIdField(required=True, dsp=DSP.SPOTIFY, validate=validate.Length(max=50)) playlistUri = fields.String(attribute="uri") imageUrl = fields.String(attribute="image_url") name = fields.String() countryCode = fields.String(attribute="country_code") followers = fields.Integer() tracksLastAdded = DateStringField(attribute="playlist.dsp_update_date_time", truncate_str=True) userDisplayName = fields.String(attribute="owner.display_name", default=None) userId = fields.String(attribute="owner.username", default=None) buzzCategoryId = fields.String(attribute="owner.owner_category.owner_category_id", default=None)