""" Contains all the artist(s) routes. """ import math from pprint import pprint import random from datetime import datetime from itertools import groupby from typing import Any from flask_openapi3 import APIBlueprint, Tag from pydantic import Field from app.api.apischemas import ( AlbumLimitSchema, ArtistHashSchema, ArtistLimitSchema, TrackLimitSchema, ) from app.config import UserConfig from app.db.userdata import SimilarArtistTable from app.lib.sortlib import sort_tracks from app.serializers.album import serialize_for_card_many from app.serializers.artist import serialize_for_cards, serialize_for_card from app.serializers.track import serialize_track from app.store.albums import AlbumStore from app.store.artists import ArtistStore from app.store.tracks import TrackStore from app.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("/") def get_artist(path: ArtistHashSchema, query: GetArtistQuery): """ Get artist Returns artist data, tracks and genres for the given artisthash. """ print(query) artisthash = path.artisthash limit = query.limit entry = ArtistStore.artistmap.get(artisthash) if entry is None: return {"error": "Artist not found"}, 404 tracks = TrackStore.get_tracks_by_trackhashes(entry.trackhashes) tracks = sort_tracks(tracks, key="playcount", reverse=True) tcount = len(tracks) artist = entry.artist if artist.albumcount == 0 and tcount < 10: limit = tcount print(limit) 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("//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 albums = AlbumStore.get_albums_by_hashes(entry.albumhashes) tracks = TrackStore.get_tracks_by_trackhashes(entry.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: 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 = [a for a in albumdict.values()] 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("//tracks") def get_all_artist_tracks(path: ArtistHashSchema): """ Get artist tracks Returns all artists by a given artist. """ tracks = ArtistStore.get_artist_tracks(path.artisthash) 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("//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()) if len(similar) > limit: similar = random.sample(similar, min(limit, len(similar))) return serialize_for_cards(similar[:limit])