port: artist page

This commit is contained in:
cwilvx
2024-06-24 22:08:05 +03:00
parent 3593b205eb
commit 54a1b85d8b
7 changed files with 187 additions and 119 deletions
+122 -91
View File
@@ -2,6 +2,7 @@
Contains all the artist(s) routes. Contains all the artist(s) routes.
""" """
from itertools import groupby
import math import math
import random import random
from datetime import datetime from datetime import datetime
@@ -9,17 +10,26 @@ from datetime import datetime
from flask_jwt_extended import current_user from flask_jwt_extended import current_user
from flask_openapi3 import APIBlueprint, Tag from flask_openapi3 import APIBlueprint, Tag
from pydantic import Field from pydantic import Field
from app.api.apischemas import AlbumLimitSchema, ArtistHashSchema, ArtistLimitSchema, TrackLimitSchema from app.api.apischemas import (
AlbumLimitSchema,
ArtistHashSchema,
ArtistLimitSchema,
TrackLimitSchema,
)
from app.config import UserConfig
from app.db import AlbumTable, ArtistTable, TrackTable
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
from app.db.sqlite.lastfm.similar_artists import SQLiteLastFMSimilarArtists as fmdb from app.db.sqlite.lastfm.similar_artists import SQLiteLastFMSimilarArtists as fmdb
from app.models import Album, FavType from app.models import Album, FavType
from app.serializers.album import serialize_for_card_many from app.serializers.album import serialize_for_card_many
from app.serializers.track import serialize_tracks from app.serializers.track import serialize_tracks
from app.store.albums import AlbumStore from app.store.albums import AlbumStore
from app.store.artists import ArtistStore from app.store.artists import ArtistStore
from app.store.tracks import TrackStore from app.store.tracks import TrackStore
bp_tag = Tag(name="Artist", description="Single artist") bp_tag = Tag(name="Artist", description="Single artist")
api = APIBlueprint("artist", __name__, url_prefix="/artist", abp_tags=[bp_tag]) api = APIBlueprint("artist", __name__, url_prefix="/artist", abp_tags=[bp_tag])
@@ -34,35 +44,22 @@ def get_artist(path: ArtistHashSchema, query: TrackLimitSchema):
artisthash = path.artisthash artisthash = path.artisthash
limit = query.limit limit = query.limit
artist = ArtistStore.get_artist_by_hash(artisthash) artist = ArtistTable.get_artist_by_hash(artisthash)
print(artist)
if artist is None: if artist is None:
return {"error": "Artist not found"}, 404 return {"error": "Artist not found"}, 404
tracks = TrackStore.get_tracks_by_artisthash(artisthash) tracks = TrackTable.get_tracks_by_artisthash(artisthash)
tcount = len(tracks) tcount = len(tracks)
acount = AlbumStore.count_albums_by_artisthash(artisthash)
if acount == 0 and tcount < 10: if artist.albumcount == 0 and tcount < 10:
limit = tcount limit = tcount
artist.set_trackcount(tcount) # artist.is_favorite = favdb.check_is_favorite(artisthash, FavType.artist)
artist.set_albumcount(acount)
artist.set_duration(sum(t.duration for t in tracks))
artist.is_favorite = favdb.check_is_favorite(artisthash, FavType.artist)
genres = set()
for t in tracks:
if t.genre is not None:
genres = genres.union(t.genre)
genres = list(genres)
try: try:
min_stamp = min(t.date for t in tracks) year = datetime.fromtimestamp(artist.date).year
year = datetime.fromtimestamp(min_stamp).year
except ValueError: except ValueError:
year = 0 year = 0
@@ -73,12 +70,11 @@ def get_artist(path: ArtistHashSchema, query: TrackLimitSchema):
decade = str(decade)[2:] + "s" decade = str(decade)[2:] + "s"
if decade: if decade:
genres.insert(0, decade) artist.genres.insert(0, {"name": decade, "genrehash": decade})
return { return {
"artist": artist, "artist": artist,
"tracks": serialize_tracks(tracks[:limit]), "tracks": serialize_tracks(tracks[:limit]),
"genres": genres,
} }
@@ -98,83 +94,118 @@ def get_artist_albums(path: ArtistHashSchema, query: GetArtistAlbumsQuery):
limit = query.limit limit = query.limit
all_albums = AlbumStore.get_albums_by_artisthash(artisthash) artist = ArtistTable.get_artist_by_hash(artisthash)
# start: check for missing albums. ie. compilations and features
all_tracks = TrackStore.get_tracks_by_artisthash(artisthash)
track_albums = set(t.albumhash for t in all_tracks)
missing_album_hashes = track_albums.difference(set(a.albumhash for a in all_albums))
if len(missing_album_hashes) > 0:
missing_albums = AlbumStore.get_albums_by_hashes(list(missing_album_hashes))
all_albums.extend(missing_albums)
# end check
def get_album_tracks(albumhash: str):
tracks = [t for t in all_tracks if t.albumhash == albumhash]
if len(tracks) > 0:
return tracks
return TrackStore.get_tracks_by_albumhash(albumhash)
for a in all_albums:
a.check_type()
album_tracks = get_album_tracks(a.albumhash)
if len(album_tracks) == 0:
continue
a.get_date_from_tracks(album_tracks)
if a.date == 0:
AlbumStore.remove_album_by_hash(a.albumhash)
continue
a.is_single(album_tracks)
all_albums = sorted(all_albums, key=lambda a: str(a.date), reverse=True)
singles = [a for a in all_albums if a.is_single]
eps = [a for a in all_albums if a.is_EP]
def remove_EPs_and_singles(albums_: list[Album]):
albums_ = [a for a in albums_ if not a.is_single]
albums_ = [a for a in albums_ if not a.is_EP]
return albums_
albums = filter(lambda a: artisthash in a.albumartists_hashes, all_albums)
albums = list(albums)
albums = remove_EPs_and_singles(albums)
compilations = [a for a in albums if a.is_compilation]
for c in compilations:
albums.remove(c)
appearances = filter(lambda a: artisthash not in a.albumartists_hashes, all_albums)
appearances = list(appearances)
appearances = remove_EPs_and_singles(appearances)
artist = ArtistStore.get_artist_by_hash(artisthash)
if artist is None: if artist is None:
return {"error": "Artist not found"}, 404 return {"error": "Artist not found"}, 404
albums = AlbumTable.get_albums_by_artisthash(artisthash)
tracks = TrackTable.get_tracks_by_artisthash(artisthash)
missing_albumhashes = {
t.albumhash for t in tracks if t.albumhash not in {a.albumhash for a in albums}
}
albums.extend(AlbumTable.get_albums_by_hash(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)
# all_albums = AlbumStore.get_albums_by_artisthash(artisthash)
# start: check for missing albums. ie. compilations and features
# all_tracks = TrackStore.get_tracks_by_artisthash(artisthash)
# track_albums = set(t.albumhash for t in all_tracks)
# missing_album_hashes = track_albums.difference(set(a.albumhash for a in all_albums))
# if len(missing_album_hashes) > 0:
# missing_albums = AlbumStore.get_albums_by_hashes(list(missing_album_hashes))
# all_albums.extend(missing_albums)
# end check
# def get_album_tracks(albumhash: str):
# tracks = [t for t in all_tracks if t.albumhash == albumhash]
# if len(tracks) > 0:
# return tracks
# return TrackStore.get_tracks_by_albumhash(albumhash)
# for a in all_albums:
# a.check_type()
# album_tracks = get_album_tracks(a.albumhash)
# if len(album_tracks) == 0:
# continue
# a.get_date_from_tracks(album_tracks)
# if a.date == 0:
# AlbumStore.remove_album_by_hash(a.albumhash)
# continue
# a.is_single(album_tracks)
albums = [a for a in albumdict.values()]
all_albums = sorted(albums, key=lambda a: str(a.date), reverse=True)
res = {
"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:
res["appearances"].append(album)
else:
res["albums"].append(album)
# def remove_EPs_and_singles(albums_: list[Album]):
# albums_ = [a for a in albums_ if not a.type == "single"]
# albums_ = [a for a in albums_ if not a.type == "ep"]
# return albums_
# albums = filter(lambda a: artisthash in missing_albumhashes, all_albums)
# albums = list(albums)
# albums = remove_EPs_and_singles(albums)
# compilations = [a for a in albums if a.is_compilation]
# for c in compilations:
# albums.remove(c)
# appearances = filter(lambda a: artisthash not in a.albumartists_hashes, all_albums)
# appearances = list(appearances)
# appearances = remove_EPs_and_singles(appearances)
# artist = ArtistStore.get_artist_by_hash(artisthash)
# if artist is None:
# return {"error": "Artist not found"}, 404
if return_all: if return_all:
limit = len(all_albums) limit = len(all_albums)
singles_and_eps = singles + eps # loop through the res dict and serialize the albums
for key, value in res.items():
res[key] = serialize_for_card_many(value[:limit])
return { res["artistname"] = artist.name
"artistname": artist.name, return res
"albums": serialize_for_card_many(albums[:limit]),
"singles_and_eps": serialize_for_card_many(singles_and_eps[:limit]),
"appearances": serialize_for_card_many(appearances[:limit]),
"compilations": serialize_for_card_many(compilations[:limit]),
}
@api.get("/<artisthash>/tracks") @api.get("/<artisthash>/tracks")
+43
View File
@@ -24,6 +24,7 @@ from sqlalchemy.orm import (
from app.models import Track as TrackModel from app.models import Track as TrackModel
from app.models import Album as AlbumModel from app.models import Album as AlbumModel
from app.models import Artist as ArtistModel
from app.utils.remove_duplicates import remove_duplicates from app.utils.remove_duplicates import remove_duplicates
@@ -100,12 +101,21 @@ class ArtistTable(Base):
result = conn.execute(select(cls).offset(start).limit(limit)) result = conn.execute(select(cls).offset(start).limit(limit))
return albums_to_dataclasses(result.fetchall()) return albums_to_dataclasses(result.fetchall())
@classmethod
def get_artist_by_hash(cls, artisthash: str):
with DbManager() as conn:
result = conn.execute(
select(ArtistTable).where(ArtistTable.artisthash == artisthash)
)
return artist_to_dataclass(result.fetchone())
class AlbumTable(Base): class AlbumTable(Base):
__tablename__ = "album" __tablename__ = "album"
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
albumartists: Mapped[list[dict[str, str]]] = mapped_column(JSON(), index=True) albumartists: Mapped[list[dict[str, str]]] = mapped_column(JSON(), index=True)
artisthashes: Mapped[list[str]] = mapped_column(JSON(), index=True)
albumhash: Mapped[str] = mapped_column(String(), unique=True, index=True) albumhash: Mapped[str] = mapped_column(String(), unique=True, index=True)
base_title: Mapped[str] = mapped_column(String()) base_title: Mapped[str] = mapped_column(String())
color: Mapped[Optional[str]] = mapped_column(String()) color: Mapped[Optional[str]] = mapped_column(String())
@@ -128,6 +138,14 @@ class AlbumTable(Base):
if album: if album:
return album_to_dataclass(album) return album_to_dataclass(album)
@classmethod
def get_albums_by_hash(cls, hashes: set[str]):
with DbManager() as conn:
result = conn.execute(
select(AlbumTable).where(AlbumTable.albumhash.in_(hashes))
)
return albums_to_dataclasses(result.fetchall())
@classmethod @classmethod
def get_all(cls, start: int, limit: int): def get_all(cls, start: int, limit: int):
with DbManager() as conn: with DbManager() as conn:
@@ -157,6 +175,14 @@ class AlbumTable(Base):
) )
return albums_to_dataclasses(result.fetchall()) return albums_to_dataclasses(result.fetchall())
@classmethod
def get_albums_by_artisthash(cls, artisthash: str):
with DbManager() as conn:
result = conn.execute(
select(AlbumTable).where(AlbumTable.artisthashes.contains(artisthash))
)
return albums_to_dataclasses(result.all())
class TrackTable(Base): class TrackTable(Base):
__tablename__ = "track" __tablename__ = "track"
@@ -165,6 +191,7 @@ class TrackTable(Base):
album: Mapped[str] = mapped_column(String()) album: Mapped[str] = mapped_column(String())
albumartists: Mapped[list[dict[str, str]]] = mapped_column(JSON()) albumartists: Mapped[list[dict[str, str]]] = mapped_column(JSON())
albumhash: Mapped[str] = mapped_column(String(), index=True) albumhash: Mapped[str] = mapped_column(String(), index=True)
artisthashes: Mapped[list[str]] = mapped_column(JSON(), index=True)
artists: Mapped[list[dict[str, str]]] = mapped_column(JSON(), index=True) artists: Mapped[list[dict[str, str]]] = mapped_column(JSON(), index=True)
bitrate: Mapped[int] = mapped_column(Integer()) bitrate: Mapped[int] = mapped_column(Integer())
copyright: Mapped[Optional[str]] = mapped_column(String()) copyright: Mapped[Optional[str]] = mapped_column(String())
@@ -237,10 +264,26 @@ class TrackTable(Base):
if track: if track:
return track_to_dataclass(track) return track_to_dataclass(track)
@classmethod
def get_tracks_by_artisthash(cls, artisthash: str):
with DbManager() as conn:
result = conn.execute(
select(TrackTable).where(TrackTable.artists.contains(artisthash))
)
return tracks_to_dataclasses(result.fetchall())
# SECTION: HELPER FUNCTIONS # SECTION: HELPER FUNCTIONS
def artist_to_dataclass(artist: Any):
return ArtistModel(**artist._asdict())
def artists_to_dataclasses(artists: Any):
return [artist_to_dataclass(artist) for artist in artists]
def album_to_dataclass(album: Any): def album_to_dataclass(album: Any):
return AlbumModel(**album._asdict()) return AlbumModel(**album._asdict())
+3 -2
View File
@@ -41,6 +41,7 @@ class IndexAlbums:
if track.albumhash not in albums: if track.albumhash not in albums:
albums[track.albumhash] = { albums[track.albumhash] = {
"albumartists": track.albumartists, "albumartists": track.albumartists,
"artisthashes": [a['artisthash'] for a in track.albumartists],
"albumhash": track.albumhash, "albumhash": track.albumhash,
"base_title": None, "base_title": None,
"color": None, "color": None,
@@ -107,7 +108,7 @@ class IndexArtists:
"dates": [track.date], "dates": [track.date],
"date": None, "date": None,
"duration": track.duration, "duration": track.duration,
"genres": [*track.genre] if track.genre else [], "genres": track.genre if track.genre else [],
"name": artist["name"], "name": artist["name"],
"trackcount": None, "trackcount": None,
"tracks": {track.trackhash}, "tracks": {track.trackhash},
@@ -121,7 +122,7 @@ class IndexArtists:
artist["created_dates"].append(track.last_mod) artist["created_dates"].append(track.last_mod)
if track.genre: if track.genre:
artist["genres"].append(track.genre) artist["genres"].extend(track.genre)
for artist in artists.values(): for artist in artists.values():
+3
View File
@@ -262,6 +262,9 @@ def get_tags(filepath: str):
for a in split_albumartists for a in split_albumartists
] ]
tags.artisthashes = list({a["artisthash"] for a in tags.artists + tags.albumartists})
# remove prod by # remove prod by
if config.removeProdBy: if config.removeProdBy:
new_title = remove_prod(new_title) new_title = remove_prod(new_title)
+1
View File
@@ -20,6 +20,7 @@ class Album:
id: int id: int
albumartists: list[dict[str, str]] albumartists: list[dict[str, str]]
albumhash: str albumhash: str
artisthashes: list[str]
base_title: str base_title: str
color: str color: str
created_date: int created_date: int
+12 -26
View File
@@ -31,33 +31,19 @@ class ArtistMinimal:
@dataclass(slots=True) @dataclass(slots=True)
class Artist(ArtistMinimal): class Artist:
""" """
Artist class Artist class
""" """
name: str = "" id: str
trackcount: int = 0 name: str
albumcount: int = 0 albumcount: int
duration: int = 0 artisthash: str
colors: list[str] = dataclasses.field(default_factory=list) created_date: int
is_favorite: bool = False date: int
created_date: float = 0.0 duration: int
genres: list[dict[str, str]]
def __post_init__(self): name: str
super(Artist, self).__init__(self.name) trackcount: int
is_favorite: bool
def set_trackcount(self, count: int):
self.trackcount = count
def set_albumcount(self, count: int):
self.albumcount = count
def set_duration(self, duration: int):
self.duration = duration
def set_colors(self, colors: list[str]):
self.colors = colors
def set_created_date(self, created_date: float):
self.created_date = created_date
+3
View File
@@ -27,6 +27,7 @@ class Track:
album: str album: str
albumartists: list[dict[str, str]] albumartists: list[dict[str, str]]
albumhash: str albumhash: str
artisthashes: list[str]
artists: str artists: str
bitrate: int bitrate: int
copyright: str copyright: str
@@ -44,9 +45,11 @@ class Track:
trackhash: str trackhash: str
extra: dict extra: dict
is_favorite: bool = False
_pos: int = 0 _pos: int = 0
_ati: str = "" _ati: str = ""
# album: str # album: str
# albumartists: str | list[ArtistMinimal] # albumartists: str | list[ArtistMinimal]
# albumhash: str # albumhash: str