mirror of
https://github.com/Dvorinka/SpotifyRecAlg.git
synced 2026-06-04 12:33:03 +00:00
first commit
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
"""
|
||||
Contains all the artist(s) routes.
|
||||
"""
|
||||
|
||||
import math
|
||||
import random
|
||||
from dataclasses import replace
|
||||
from datetime import datetime
|
||||
from itertools import groupby
|
||||
from typing import Any
|
||||
|
||||
from flask_openapi3 import APIBlueprint, Tag
|
||||
from pydantic import Field
|
||||
|
||||
from swingmusic.api.apischemas import (
|
||||
AlbumLimitSchema,
|
||||
ArtistHashSchema,
|
||||
ArtistLimitSchema,
|
||||
TrackLimitSchema,
|
||||
)
|
||||
from swingmusic.config import UserConfig
|
||||
from swingmusic.db.userdata import SimilarArtistTable
|
||||
from swingmusic.lib.sortlib import sort_tracks
|
||||
from swingmusic.serializers.album import serialize_for_card_many
|
||||
from swingmusic.serializers.artist import serialize_for_card, serialize_for_cards
|
||||
from swingmusic.serializers.track import serialize_track
|
||||
from swingmusic.services.user_library_scope import (
|
||||
filter_trackhashes_for_user,
|
||||
get_available_trackhashes,
|
||||
)
|
||||
from swingmusic.store.albums import AlbumStore
|
||||
from swingmusic.store.artists import ArtistStore
|
||||
from swingmusic.store.tracks import TrackStore
|
||||
from swingmusic.utils.stats import get_track_group_stats
|
||||
|
||||
bp_tag = Tag(name="Artist", description="Single artist")
|
||||
api = APIBlueprint("artist", __name__, url_prefix="/artist", abp_tags=[bp_tag])
|
||||
|
||||
|
||||
class GetArtistAlbumsQuery(AlbumLimitSchema):
|
||||
all: bool = Field(
|
||||
description="Whether to ignore albumlimit and return all albums", default=False
|
||||
)
|
||||
|
||||
|
||||
class GetArtistQuery(TrackLimitSchema, GetArtistAlbumsQuery):
|
||||
albumlimit: int = Field(7, description="The number of albums to return")
|
||||
|
||||
|
||||
@api.get("/<string:artisthash>")
|
||||
def get_artist(path: ArtistHashSchema, query: GetArtistQuery):
|
||||
"""
|
||||
Get artist
|
||||
|
||||
Returns artist data, tracks and genres for the given artisthash.
|
||||
"""
|
||||
artisthash = path.artisthash
|
||||
limit = query.limit
|
||||
|
||||
entry = ArtistStore.artistmap.get(artisthash)
|
||||
|
||||
if entry is None:
|
||||
return {"error": "Artist not found"}, 404
|
||||
|
||||
visible_trackhashes = filter_trackhashes_for_user(entry.trackhashes)
|
||||
if not visible_trackhashes:
|
||||
return {"error": "Artist not found"}, 404
|
||||
|
||||
tracks = TrackStore.get_tracks_by_trackhashes(visible_trackhashes)
|
||||
tracks = sort_tracks(tracks, key="playcount", reverse=True)
|
||||
tcount = len(tracks)
|
||||
|
||||
artist = entry.artist
|
||||
if artist.albumcount == 0 and tcount < 10:
|
||||
limit = tcount
|
||||
|
||||
try:
|
||||
year = datetime.fromtimestamp(artist.date).year
|
||||
except ValueError:
|
||||
year = 0
|
||||
|
||||
genres = [*artist.genres]
|
||||
decade = None
|
||||
|
||||
if year:
|
||||
decade = math.floor(year / 10) * 10
|
||||
decade = str(decade)[2:] + "s"
|
||||
|
||||
if decade:
|
||||
genres.insert(0, {"name": decade, "genrehash": decade})
|
||||
|
||||
stats = get_track_group_stats(tracks)
|
||||
duration = sum(t.duration for t in tracks) if tracks else 0
|
||||
tracks = tracks[:limit] if (limit and limit != -1) else tracks
|
||||
tracks = [
|
||||
{
|
||||
**serialize_track(t),
|
||||
"help_text": (
|
||||
"unplayed"
|
||||
if t.playcount == 0
|
||||
else f"{t.playcount} play{'' if t.playcount == 1 else 's'}"
|
||||
),
|
||||
}
|
||||
for t in tracks
|
||||
]
|
||||
|
||||
query.limit = query.albumlimit
|
||||
albums = get_artist_albums(path, query)
|
||||
|
||||
return {
|
||||
"artist": {
|
||||
**serialize_for_card(artist),
|
||||
"duration": duration,
|
||||
"trackcount": tcount,
|
||||
"albumcount": artist.albumcount,
|
||||
"genres": genres,
|
||||
"is_favorite": artist.is_favorite,
|
||||
},
|
||||
"tracks": tracks,
|
||||
"albums": albums,
|
||||
"stats": stats,
|
||||
}
|
||||
|
||||
|
||||
@api.get("/<artisthash>/albums")
|
||||
def get_artist_albums(path: ArtistHashSchema, query: GetArtistAlbumsQuery):
|
||||
"""
|
||||
Get artist albums.
|
||||
"""
|
||||
return_all = query.all
|
||||
artisthash = path.artisthash
|
||||
|
||||
limit = query.limit
|
||||
|
||||
entry = ArtistStore.artistmap.get(artisthash)
|
||||
|
||||
if entry is None:
|
||||
return {"error": "Artist not found"}, 404
|
||||
|
||||
visible_trackhashes = set(filter_trackhashes_for_user(entry.trackhashes))
|
||||
if not visible_trackhashes:
|
||||
return {"error": "Artist not found"}, 404
|
||||
|
||||
albums = AlbumStore.get_albums_by_hashes(entry.albumhashes)
|
||||
tracks = TrackStore.get_tracks_by_trackhashes(visible_trackhashes)
|
||||
|
||||
missing_albumhashes = {
|
||||
t.albumhash for t in tracks if t.albumhash not in {a.albumhash for a in albums}
|
||||
}
|
||||
|
||||
albums.extend(AlbumStore.get_albums_by_hashes(missing_albumhashes))
|
||||
albumdict = {a.albumhash: replace(a) for a in albums}
|
||||
|
||||
config = UserConfig()
|
||||
albumgroups = groupby(tracks, key=lambda t: t.albumhash)
|
||||
for albumhash, tracks in albumgroups:
|
||||
album = albumdict.get(albumhash)
|
||||
|
||||
if album:
|
||||
album.check_type(list(tracks), config.showAlbumsAsSingles)
|
||||
|
||||
albums = [
|
||||
album
|
||||
for album in albumdict.values()
|
||||
if set(AlbumStore.albummap.get(album.albumhash).trackhashes).intersection(
|
||||
visible_trackhashes
|
||||
)
|
||||
]
|
||||
all_albums = sorted(albums, key=lambda a: a.date, reverse=True)
|
||||
|
||||
res: dict[str, Any] = {
|
||||
"albums": [],
|
||||
"appearances": [],
|
||||
"compilations": [],
|
||||
"singles_and_eps": [],
|
||||
}
|
||||
|
||||
for album in all_albums:
|
||||
if album.type == "single" or album.type == "ep":
|
||||
res["singles_and_eps"].append(album)
|
||||
elif album.type == "compilation":
|
||||
res["compilations"].append(album)
|
||||
elif (
|
||||
album.albumhash in missing_albumhashes
|
||||
or artisthash not in album.artisthashes
|
||||
):
|
||||
res["appearances"].append(album)
|
||||
else:
|
||||
res["albums"].append(album)
|
||||
|
||||
if return_all:
|
||||
limit = len(all_albums)
|
||||
|
||||
# loop through the res dict and serialize the albums
|
||||
for key, value in res.items():
|
||||
res[key] = serialize_for_card_many(value[:limit])
|
||||
|
||||
res["artistname"] = entry.artist.name
|
||||
return res
|
||||
|
||||
|
||||
@api.get("/<artisthash>/tracks")
|
||||
def get_all_artist_tracks(path: ArtistHashSchema):
|
||||
"""
|
||||
Get artist tracks
|
||||
|
||||
Returns all artists by a given artist.
|
||||
"""
|
||||
entry = ArtistStore.artistmap.get(path.artisthash)
|
||||
if entry is None:
|
||||
return []
|
||||
|
||||
visible_trackhashes = filter_trackhashes_for_user(entry.trackhashes)
|
||||
tracks = TrackStore.get_tracks_by_trackhashes(visible_trackhashes)
|
||||
tracks = sort_tracks(tracks, key="playcount", reverse=True)
|
||||
tracks = [
|
||||
{
|
||||
**serialize_track(t),
|
||||
"help_text": (
|
||||
"unplayed"
|
||||
if t.playcount == 0
|
||||
else f"{t.playcount} play{'' if t.playcount == 1 else 's'}"
|
||||
),
|
||||
}
|
||||
for t in tracks
|
||||
]
|
||||
|
||||
return tracks
|
||||
|
||||
|
||||
@api.get("/<artisthash>/similar")
|
||||
def get_similar_artists(path: ArtistHashSchema, query: ArtistLimitSchema):
|
||||
"""
|
||||
Get similar artists.
|
||||
"""
|
||||
limit = query.limit
|
||||
result = SimilarArtistTable.get_by_hash(path.artisthash)
|
||||
|
||||
if result is None:
|
||||
return []
|
||||
|
||||
similar = ArtistStore.get_artists_by_hashes(result.get_artist_hash_set())
|
||||
available_trackhashes = get_available_trackhashes()
|
||||
similar = [
|
||||
artist
|
||||
for artist in similar
|
||||
if set(ArtistStore.artistmap.get(artist.artisthash).trackhashes).intersection(
|
||||
available_trackhashes
|
||||
)
|
||||
]
|
||||
|
||||
if len(similar) > limit:
|
||||
similar = random.sample(similar, min(limit, len(similar)))
|
||||
|
||||
return serialize_for_cards(similar[:limit])
|
||||
Reference in New Issue
Block a user