rewrite album and artist stores using in-mem hashmap stores

This commit is contained in:
cwilvx
2024-07-15 00:26:56 +03:00
parent 83e105a198
commit 58c90d95b1
11 changed files with 297 additions and 237 deletions
+36 -27
View File
@@ -4,26 +4,24 @@ Contains all the album routes.
import random import random
from pydantic import Field from pydantic import BaseModel, Field
from flask_openapi3 import Tag from flask_openapi3 import Tag
from flask_openapi3 import APIBlueprint from flask_openapi3 import APIBlueprint
from app.api.apischemas import AlbumHashSchema, AlbumLimitSchema, ArtistHashSchema from app.api.apischemas import AlbumHashSchema, AlbumLimitSchema, ArtistHashSchema
from app.config import UserConfig from app.config import UserConfig
from app.db.libdata import ArtistTable
from app.db.libdata import AlbumTable as AlbumDb, TrackTable as TrackDb
from app.db.userdata import SimilarArtistTable from app.db.userdata import SimilarArtistTable
from app.models.album import Album
from app.settings import Defaults from app.settings import Defaults
from app.utils import flatten from app.store.albums import AlbumStore
from app.store.artists import ArtistStore
from app.store.tracks import TrackStore
from app.utils.hashing import create_hash from app.utils.hashing import create_hash
from app.lib.albumslib import sort_by_track_no from app.lib.albumslib import sort_by_track_no
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.db.sqlite.albumcolors import SQLiteAlbumMethods as adb
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 lastfmdb
get_albums_by_albumartist = adb.get_albums_by_albumartist
check_is_fav = favdb.check_is_favorite check_is_fav = favdb.check_is_favorite
bp_tag = Tag(name="Album", description="Single album") bp_tag = Tag(name="Album", description="Single album")
@@ -39,12 +37,14 @@ def get_album_tracks_and_info(body: AlbumHashSchema):
Returns album info and tracks for the given albumhash. Returns album info and tracks for the given albumhash.
""" """
albumhash = body.albumhash albumhash = body.albumhash
album = AlbumDb.get_album_by_albumhash(albumhash) # album = AlbumDb.get_album_by_albumhash(albumhash)
albumentry = AlbumStore.albummap.get(albumhash)
if album is None: if albumentry is None:
return {"error": "Album not found"}, 404 return {"error": "Album not found"}, 404
tracks = TrackDb.get_tracks_by_albumhash(albumhash) album = albumentry.album
tracks = TrackStore.get_tracks_by_trackhashes(albumentry.trackhashes)
album.trackcount = len(tracks) album.trackcount = len(tracks)
album.duration = sum(t.duration for t in tracks) album.duration = sum(t.duration for t in tracks)
album.check_type( album.check_type(
@@ -52,6 +52,7 @@ def get_album_tracks_and_info(body: AlbumHashSchema):
) )
track_total = sum({int(t.extra.get("track_total", 1) or 1) for t in tracks}) track_total = sum({int(t.extra.get("track_total", 1) or 1) for t in tracks})
avg_bitrate = sum(t.bitrate for t in tracks) // (len(tracks) or 1)
return { return {
"info": album, "info": album,
@@ -61,7 +62,7 @@ def get_album_tracks_and_info(body: AlbumHashSchema):
# 1. All the tracks have the correct track totals # 1. All the tracks have the correct track totals
# 2. Tracks with the same track total are from the same disc # 2. Tracks with the same track total are from the same disc
"track_total": track_total, "track_total": track_total,
"avg_bitrate": sum(t.bitrate for t in tracks) // len(tracks), "avg_bitrate": avg_bitrate,
}, },
"copyright": tracks[0].copyright, "copyright": tracks[0].copyright,
"tracks": serialize_tracks(tracks, remove_disc=False), "tracks": serialize_tracks(tracks, remove_disc=False),
@@ -76,7 +77,7 @@ def get_album_tracks(path: AlbumHashSchema):
Returns all the tracks in the given album, sorted by disc and track number. Returns all the tracks in the given album, sorted by disc and track number.
NOTE: No album info is returned. NOTE: No album info is returned.
""" """
tracks = TrackDb.get_tracks_by_albumhash(path.albumhash) tracks = AlbumStore.get_album_tracks(path.albumhash)
tracks = sort_by_track_no(tracks) tracks = sort_by_track_no(tracks)
return serialize_tracks(tracks) return serialize_tracks(tracks)
@@ -105,7 +106,11 @@ def get_more_from_artist(body: GetMoreFromArtistsBody):
limit = body.limit limit = body.limit
base_title = body.base_title base_title = body.base_title
all_albums = AlbumDb.get_albums_by_artisthashes(albumartists) all_albums: dict[str, list[Album]] = {}
for artisthash in albumartists:
all_albums[artisthash] = AlbumStore.get_albums_by_artisthash(artisthash)
seen_hashes = set() seen_hashes = set()
for artisthash, albums in all_albums.items(): for artisthash, albums in all_albums.items():
@@ -128,14 +133,15 @@ def get_more_from_artist(body: GetMoreFromArtistsBody):
return all_albums return all_albums
class GetAlbumVersionsBody(ArtistHashSchema): class GetAlbumVersionsBody(BaseModel):
og_album_title: str = Field( og_album_title: str = Field(
description="The original album title (album.og_title)", description="The original album title (album.og_title)",
example=Defaults.API_ALBUMNAME, example=Defaults.API_ALBUMNAME,
) )
base_title: str = Field(
description="The base title of the album to exclude from the results.", albumhash: str = Field(
example=Defaults.API_ALBUMNAME, description="The album hash of the album to exclude from the results.",
example=Defaults.API_ALBUMHASH,
) )
@@ -146,18 +152,23 @@ def get_album_versions(body: GetAlbumVersionsBody):
Returns other versions of the given album. Returns other versions of the given album.
""" """
og_album_title = body.og_album_title albumhash = body.albumhash
base_title = body.base_title
artisthash = body.artisthash
albums = AlbumDb.get_albums_by_base_title(base_title) album = AlbumStore.albummap.get(albumhash)
if not album:
return []
artisthash = album.album.artisthashes[0]
albums = AlbumStore.get_albums_by_artisthash(artisthash)
basetitle = album.basetitle
albums = [ albums = [
a a
for a in albums for a in albums
if a.og_title != og_album_title if a.og_title != album.album.og_title
if a.base_title == basetitle
and artisthash in {a["artisthash"] for a in a.albumartists} and artisthash in {a["artisthash"] for a in a.albumartists}
] ]
print(albums)
return serialize_for_card_many(albums) return serialize_for_card_many(albums)
@@ -181,10 +192,8 @@ def get_similar_albums(query: GetSimilarAlbumsQuery):
return [] return []
artisthashes = similar_artists.get_artist_hash_set() artisthashes = similar_artists.get_artist_hash_set()
artists = ArtistTable.get_artists_by_artisthashes(artisthashes) artists = ArtistStore.get_artists_by_hashes(artisthashes)
albums = AlbumStore.get_albums_by_artisthashes([a.artisthash for a in artists])
albums = AlbumDb.get_albums_by_artisthashes([a.artisthash for a in artists])
albums = flatten(albums.values())
sample = random.sample(albums, min(len(albums), limit)) sample = random.sample(albums, min(len(albums), limit))
return serialize_for_card_many(sample[:limit]) return serialize_for_card_many(sample[:limit])
+17 -14
View File
@@ -17,14 +17,16 @@ from app.api.apischemas import (
) )
from app.config import UserConfig from app.config import UserConfig
from app.db.libdata import ArtistTable
from app.db.libdata import AlbumTable, TrackTable
from app.db.userdata import SimilarArtistTable from app.db.userdata import SimilarArtistTable
from app.serializers.album import serialize_for_card_many from app.serializers.album import serialize_for_card_many
from app.serializers.artist import serialize_for_cards from app.serializers.artist import serialize_for_cards
from app.serializers.track import serialize_tracks from app.serializers.track import serialize_tracks
from app.store.albums import AlbumStore
from app.store.artists import ArtistStore
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])
@@ -39,13 +41,15 @@ def get_artist(path: ArtistHashSchema, query: TrackLimitSchema):
artisthash = path.artisthash artisthash = path.artisthash
limit = query.limit limit = query.limit
artist = ArtistTable.get_artist_by_hash(artisthash) entry = ArtistStore.artistmap.get(artisthash)
if artist is None:
if entry is None:
return {"error": "Artist not found"}, 404 return {"error": "Artist not found"}, 404
tracks = TrackTable.get_tracks_by_artisthash(artisthash) tracks = TrackStore.get_tracks_by_trackhashes(entry.trackhashes)
tcount = len(tracks) tcount = len(tracks)
artist = entry.artist
if artist.albumcount == 0 and tcount < 10: if artist.albumcount == 0 and tcount < 10:
limit = tcount limit = tcount
@@ -85,19 +89,19 @@ def get_artist_albums(path: ArtistHashSchema, query: GetArtistAlbumsQuery):
limit = query.limit limit = query.limit
artist = ArtistTable.get_artist_by_hash(artisthash) entry = ArtistStore.artistmap.get(artisthash)
if artist is None: if entry is None:
return {"error": "Artist not found"}, 404 return {"error": "Artist not found"}, 404
albums = AlbumTable.get_albums_by_artisthash(artisthash) albums = AlbumStore.get_albums_by_hashes(entry.albumhashes)
tracks = TrackTable.get_tracks_by_artisthash(artisthash) tracks = TrackStore.get_tracks_by_trackhashes(entry.trackhashes)
missing_albumhashes = { missing_albumhashes = {
t.albumhash for t in tracks if t.albumhash not in {a.albumhash for a in albums} t.albumhash for t in tracks if t.albumhash not in {a.albumhash for a in albums}
} }
albums.extend(AlbumTable.get_albums_by_albumhashes(missing_albumhashes)) albums.extend(AlbumStore.get_albums_by_hashes(missing_albumhashes))
albumdict = {a.albumhash: a for a in albums} albumdict = {a.albumhash: a for a in albums}
config = UserConfig() config = UserConfig()
@@ -135,7 +139,7 @@ def get_artist_albums(path: ArtistHashSchema, query: GetArtistAlbumsQuery):
for key, value in res.items(): for key, value in res.items():
res[key] = serialize_for_card_many(value[:limit]) res[key] = serialize_for_card_many(value[:limit])
res["artistname"] = artist.name res["artistname"] = entry.artist.name
return res return res
@@ -146,8 +150,7 @@ def get_all_artist_tracks(path: ArtistHashSchema):
Returns all artists by a given artist. Returns all artists by a given artist.
""" """
# tracks = TrackStore.get_tracks_by_artisthash(path.artisthash) tracks = ArtistStore.get_artist_tracks(path.artisthash)
tracks = TrackTable.get_tracks_by_artisthash(path.artisthash)
return serialize_tracks(tracks) return serialize_tracks(tracks)
@@ -162,7 +165,7 @@ def get_similar_artists(path: ArtistHashSchema, query: ArtistLimitSchema):
if result is None: if result is None:
return [] return []
similar = ArtistTable.get_artists_by_artisthashes(result.get_artist_hash_set()) similar = ArtistStore.get_artists_by_hashes(result.get_artist_hash_set())
if len(similar) > limit: if len(similar) > limit:
similar = random.sample(similar, min(limit, len(similar))) similar = random.sample(similar, min(limit, len(similar)))
-1
View File
@@ -317,7 +317,6 @@ class AlbumTable(Base):
for artist in artisthashes: for artist in artisthashes:
result = conn.execute( result = conn.execute(
# NOTE: The artist dict keys need to in the same order they appear in the db for this to work!
select(AlbumTable).where(AlbumTable.artisthashes.contains(artist)) select(AlbumTable).where(AlbumTable.artisthashes.contains(artist))
) )
albums[artist] = albums_to_dataclasses(result.fetchall()) albums[artist] = albums_to_dataclasses(result.fetchall())
+2 -3
View File
@@ -12,8 +12,7 @@ from app.db.sqlite.albumcolors import SQLiteAlbumMethods as aldb
from app.db.sqlite.artistcolors import SQLiteArtistMethods as adb from app.db.sqlite.artistcolors import SQLiteArtistMethods as adb
from app.db.sqlite.utils import SQLiteManager from app.db.sqlite.utils import SQLiteManager
from app.store.artists import ArtistStore # from app.store.artists import ArtistStore
from app.store.albums import AlbumStore
from app.logger import log from app.logger import log
from app.lib.errors import PopulateCancelledError from app.lib.errors import PopulateCancelledError
from app.utils.progressbar import tqdm from app.utils.progressbar import tqdm
@@ -101,7 +100,7 @@ class ProcessArtistColors:
""" """
def __init__(self, instance_key: str) -> None: def __init__(self, instance_key: str) -> None:
all_artists = [a for a in ArtistStore.artists if len(a.colors) == 0] # all_artists = [a for a in ArtistStore.artists if len(a.colors) == 0]
global PROCESS_ARTIST_COLORS_KEY global PROCESS_ARTIST_COLORS_KEY
PROCESS_ARTIST_COLORS_KEY = instance_key PROCESS_ARTIST_COLORS_KEY = instance_key
+121 -122
View File
@@ -2,13 +2,14 @@ import gc
import os import os
from pprint import pprint from pprint import pprint
from time import time from time import time
from typing import Generator
from app import settings from app import settings
from app.config import UserConfig from app.config import UserConfig
from app.db.libdata import ArtistTable from app.db.libdata import ArtistTable
from app.db.libdata import AlbumTable, TrackTable from app.db.libdata import TrackTable
from app.lib.populate import CordinateMedia from app.lib.populate import CordinateMedia
from app.lib.taglib import extract_thumb, get_tags from app.lib.taglib import extract_thumb, get_tags
from app.models.album import Album
from app.models.artist import Artist
from app.models.track import Track from app.models.track import Track
from app.store.folder import FolderStore from app.store.folder import FolderStore
from app.utils.filesystem import run_fast_scandir from app.utils.filesystem import run_fast_scandir
@@ -141,154 +142,152 @@ class IndexTracks:
print("Done") print("Done")
class IndexAlbums: # class IndexAlbums:
def __init__(self) -> None: def create_albums():
albums = dict() albums = dict()
all_tracks: list[Track] = TrackTable.get_all() all_tracks: list[Track] = TrackTable.get_all()
if len(all_tracks) == 0: for track in all_tracks:
return if track.albumhash not in albums:
albums[track.albumhash] = {
"albumartists": track.albumartists,
"artisthashes": [a["artisthash"] for a in track.albumartists],
"albumhash": track.albumhash,
"base_title": None,
"color": None,
"created_date": track.last_mod,
"date": track.date,
"duration": track.duration,
"genres": [*track.genres] if track.genres else [],
"og_title": track.og_album,
"lastplayed": track.lastplayed,
"playcount": track.playcount,
"playduration": track.playduration,
"title": track.album,
"trackcount": 1,
"extra": {}
}
else:
album = albums[track.albumhash]
album["trackcount"] += 1
album["playcount"] += track.playcount
album["playduration"] += track.playduration
album["lastplayed"] = max(album["lastplayed"], track.lastplayed)
album["duration"] += track.duration
album["date"] = min(album["date"], track.date)
album["created_date"] = min(album["created_date"], track.last_mod)
for track in all_tracks: if track.genres:
if track.albumhash not in albums: album["genres"].extend(track.genres)
albums[track.albumhash] = {
"albumartists": track.albumartists, for album in albums.values():
"artisthashes": [a["artisthash"] for a in track.albumartists], genres = []
"albumhash": track.albumhash, for genre in album["genres"]:
"base_title": None, if genre not in genres:
"color": None, genres.append(genre)
album["genres"] = genres
album["genrehashes"] = " ".join([g['genrehash'] for g in genres])
album["base_title"], _ = get_base_album_title(album["og_title"])
del genres
# AlbumTable.remove_all()
# AlbumTable.insert_many(list(albums.values()))
return [Album(**album) for album in albums.values()]
# class IndexArtists:
def create_artists():
all_tracks: list[Track] = TrackTable.get_all()
artists = dict()
for track in all_tracks:
this_artists = track.artists
for a in track.albumartists:
if a not in this_artists:
a["in_track"] = False
this_artists.append(a)
for thisartist in this_artists:
if thisartist["artisthash"] not in artists:
artists[thisartist["artisthash"]] = {
"albumcount": None,
"albums": {track.albumhash},
"artisthash": thisartist["artisthash"],
"created_date": track.last_mod, "created_date": track.last_mod,
"date": track.date, "date": track.date,
"duration": track.duration, "duration": track.duration,
"genres": [*track.genres] if track.genres else [], "genres": track.genres if track.genres else [],
"og_title": track.og_album, "name": None,
"names": {thisartist["name"]},
"lastplayed": track.lastplayed, "lastplayed": track.lastplayed,
"playcount": track.playcount, "playcount": track.playcount,
"playduration": track.playduration, "playduration": track.playduration,
"title": track.album, "trackcount": None,
"trackcount": 1, "tracks": (
{track.trackhash}
if thisartist.get("in_track", True)
else set()
),
"extra": {},
} }
else: else:
album = albums[track.albumhash] artist = artists[thisartist["artisthash"]]
album["trackcount"] += 1 artist["duration"] += track.duration
album["playcount"] += track.playcount artist["playcount"] += track.playcount
album["playduration"] += track.playduration artist["playduration"] += track.playduration
album["lastplayed"] = max(album["lastplayed"], track.lastplayed) artist["albums"].add(track.albumhash)
album["duration"] += track.duration artist["date"] = min(artist["date"], track.date)
album["date"] = min(album["date"], track.date) artist["lastplayed"] = max(artist["lastplayed"], track.lastplayed)
album["created_date"] = min(album["created_date"], track.last_mod) artist["created_date"] = min(artist["created_date"], track.last_mod)
artist["names"].add(thisartist["name"])
if thisartist.get("in_track", True):
artist["tracks"].add(track.trackhash)
if track.genres: if track.genres:
album["genres"].extend(track.genres) artist["genres"].extend(track.genres)
for album in albums.values(): for artist in artists.values():
genres = [] artist["albumcount"] = len(artist["albums"])
for genre in album["genres"]: artist["trackcount"] = len(artist["tracks"])
if genre not in genres:
genres.append(genre)
album["genres"] = genres genres = []
album["base_title"], _ = get_base_album_title(album["og_title"])
del genres for genre in artist["genres"]:
if genre not in genres:
genres.append(genre)
AlbumTable.remove_all() artist["genres"] = genres
AlbumTable.insert_many(list(albums.values())) artist["genrehashes"] = " ".join([g['genrehash'] for g in genres])
del albums artist["name"] = sorted(artist["names"])[0]
# INFO: Delete temporary keys
del artist["names"]
del artist["tracks"]
del artist["albums"]
class IndexArtists: # INFO: Delete local variables
def __init__(self) -> None: del genres
all_tracks: list[Track] = TrackTable.get_all()
artists = dict()
if len(all_tracks) == 0: # ArtistTable.remove_all()
return # ArtistTable.insert_many(list(artists.values()))
# del artists
for track in all_tracks: return [Artist(**artist) for artist in artists.values()]
this_artists = track.artists
for a in track.albumartists:
if a not in this_artists:
a["in_track"] = False
this_artists.append(a)
for thisartist in this_artists:
if thisartist["artisthash"] not in artists:
artists[thisartist["artisthash"]] = {
"albumcount": None,
"albums": {track.albumhash},
"artisthash": thisartist["artisthash"],
"created_date": track.last_mod,
"date": track.date,
"duration": track.duration,
"genres": track.genres if track.genres else [],
"name": None,
"names": {thisartist["name"]},
"lastplayed": track.lastplayed,
"playcount": track.playcount,
"playduration": track.playduration,
"trackcount": None,
"tracks": (
{track.trackhash}
if thisartist.get("in_track", True)
else set()
),
}
else:
artist = artists[thisartist["artisthash"]]
artist["duration"] += track.duration
artist["playcount"] += track.playcount
artist["playduration"] += track.playduration
artist["albums"].add(track.albumhash)
artist["date"] = min(artist["date"], track.date)
artist["lastplayed"] = max(artist["lastplayed"], track.lastplayed)
artist["created_date"] = min(artist["created_date"], track.last_mod)
artist["names"].add(thisartist["name"])
if thisartist.get("in_track", True):
artist["tracks"].add(track.trackhash)
if track.genres:
artist["genres"].extend(track.genres)
for artist in artists.values():
artist["albumcount"] = len(artist["albums"])
artist["trackcount"] = len(artist["tracks"])
genres = []
for genre in artist["genres"]:
if genre not in genres:
genres.append(genre)
artist["genres"] = genres
artist["name"] = sorted(artist["names"])[0]
# INFO: Delete temporary keys
del artist["names"]
del artist["tracks"]
del artist["albums"]
# INFO: Delete local variables
del genres
ArtistTable.remove_all()
ArtistTable.insert_many(list(artists.values()))
del artists
class IndexEverything: class IndexEverything:
def __init__(self) -> None: def __init__(self) -> None:
IndexTracks(instance_key=time()) IndexTracks(instance_key=time())
IndexAlbums() # IndexAlbums()
IndexArtists() # IndexArtists()
FolderStore.load_filepaths() FolderStore.load_filepaths()
# pass # pass
CordinateMedia(instance_key=str(time())) # CordinateMedia(instance_key=str(time()))
gc.collect() gc.collect()
+1 -1
View File
@@ -14,7 +14,6 @@ class Album:
Creates an album object Creates an album object
""" """
id: int
albumartists: list[dict[str, str]] albumartists: list[dict[str, str]]
albumhash: str albumhash: str
artisthashes: list[str] artisthashes: list[str]
@@ -34,6 +33,7 @@ class Album:
playduration: int playduration: int
extra: dict extra: dict
id: int = -1
type: str = "album" type: str = "album"
image: str = "" image: str = ""
versions: list[str] = dataclasses.field(default_factory=list) versions: list[str] = dataclasses.field(default_factory=list)
+1 -1
View File
@@ -36,7 +36,6 @@ class Artist:
Artist class Artist class
""" """
id: str
name: str name: str
albumcount: int albumcount: int
artisthash: str artisthash: str
@@ -53,6 +52,7 @@ class Artist:
playduration: int playduration: int
extra: dict extra: dict
id: int = -1
image: str = "" image: str = ""
def __post_init__(self): def __post_init__(self):
+2
View File
@@ -47,4 +47,6 @@ def load_into_mem():
# AlbumStore.load_albums(instance_key) # AlbumStore.load_albums(instance_key)
# ArtistStore.load_artists(instance_key) # ArtistStore.load_artists(instance_key)
TrackStore.load_all_tracks(get_random_str()) TrackStore.load_all_tracks(get_random_str())
AlbumStore.load_albums('a')
ArtistStore.load_artists('a')
FolderStore.load_filepaths() FolderStore.load_filepaths()
+66 -48
View File
@@ -1,9 +1,13 @@
from itertools import groupby from itertools import groupby
import json import json
from pprint import pprint
import random import random
from typing import Iterable
from app.db.sqlite.albumcolors import SQLiteAlbumMethods as aldb from app.db.sqlite.albumcolors import SQLiteAlbumMethods as aldb
from app.lib.tagger import create_albums
from app.models import Album, Track from app.models import Album, Track
from app.store.artists import ArtistStore
from app.utils.customlist import CustomList from app.utils.customlist import CustomList
from app.utils.remove_duplicates import remove_duplicates from app.utils.remove_duplicates import remove_duplicates
@@ -14,8 +18,19 @@ from app.utils.progressbar import tqdm
ALBUM_LOAD_KEY = "" ALBUM_LOAD_KEY = ""
class AlbumMapEntry:
def __init__(self, album: Album) -> None:
self.album = album
self.trackhashes: set[str] = set()
@property
def basetitle(self):
return self.album.base_title
class AlbumStore: class AlbumStore:
albums: list[Album] = CustomList() albums: list[Album] = CustomList()
albummap: dict[str, AlbumMapEntry] = {}
@staticmethod @staticmethod
def create_album(track: Track): def create_album(track: Track):
@@ -36,43 +51,28 @@ class AlbumStore:
global ALBUM_LOAD_KEY global ALBUM_LOAD_KEY
ALBUM_LOAD_KEY = instance_key ALBUM_LOAD_KEY = instance_key
cls.albums = CustomList()
print("Loading albums... ", end="") print("Loading albums... ", end="")
tracks = remove_duplicates(TrackStore.tracks)
cls.albummap = {
album.albumhash: AlbumMapEntry(album=album) for album in create_albums()
}
tracks = remove_duplicates(TrackStore.get_flat_list())
tracks = sorted(tracks, key=lambda t: t.albumhash) tracks = sorted(tracks, key=lambda t: t.albumhash)
grouped = groupby(tracks, lambda t: t.albumhash) grouped = groupby(tracks, lambda t: t.albumhash)
for albumhash, tracks in grouped: for albumhash, tracks in grouped:
tracks = list(tracks) cls.albummap[albumhash].trackhashes = {t.trackhash for t in tracks}
sample = tracks[0]
if sample is None: # db_albums: list[tuple] = aldb.get_all_albums()
continue
count = len(list(tracks)) # for album in db_albums:
duration = sum(t.duration for t in tracks) # albumhash = album[1]
created_date = min(t.created_date for t in tracks) # colors = json.loads(album[2])
album = AlbumStore.create_album(sample) # for _al in cls.albums:
# if _al.albumhash == albumhash:
album.get_date_from_tracks(tracks) # _al.set_colors(colors)
album.set_count(count) # break
album.set_duration(duration)
album.set_created_date(created_date)
cls.albums.append(album)
db_albums: list[tuple] = aldb.get_all_albums()
for album in db_albums:
albumhash = album[1]
colors = json.loads(album[2])
for _al in cls.albums:
if _al.albumhash == albumhash:
_al.set_colors(colors)
break
print("Done!") print("Done!")
@@ -98,9 +98,7 @@ class AlbumStore:
Returns N albums by the given albumartist, excluding the specified album. Returns N albums by the given albumartist, excluding the specified album.
""" """
albums = [ albums = [album for album in cls.albums if artisthash in album.artisthashes]
album for album in cls.albums if artisthash in album.albumartists_hashes
]
albums = [ albums = [
album album
@@ -126,25 +124,11 @@ class AlbumStore:
return None return None
@classmethod @classmethod
def get_albums_by_hashes(cls, albumhashes: list[str]) -> list[Album]: def get_albums_by_hashes(cls, albumhashes: Iterable[str]) -> list[Album]:
""" """
Returns albums by their hashes. Returns albums by their hashes.
""" """
albums_str = "-".join(albumhashes) return [cls.albummap[albumhash].album for albumhash in albumhashes]
albums = [a for a in cls.albums if a.albumhash in albums_str]
# sort albums by the order of the hashes
albums.sort(key=lambda x: albumhashes.index(x.albumhash))
return albums
@classmethod
def get_albums_by_artisthash(cls, artisthash: str) -> list[Album]:
"""
Returns all albums by the given artist.
"""
return [
album for album in cls.albums if artisthash in album.albumartists_hashes
]
@classmethod @classmethod
def count_albums_by_artisthash(cls, artisthash: str): def count_albums_by_artisthash(cls, artisthash: str):
@@ -174,3 +158,37 @@ class AlbumStore:
Removes an album from the store. Removes an album from the store.
""" """
cls.albums = CustomList(a for a in cls.albums if a.albumhash != albumhash) cls.albums = CustomList(a for a in cls.albums if a.albumhash != albumhash)
@classmethod
def get_albums_by_artisthash(cls, hash: str):
"""
Returns all albums by the given artist hash.
"""
artist = ArtistStore.artistmap.get(hash)
if not artist:
return []
return [cls.albummap[albumhash].album for albumhash in artist.albumhashes]
@classmethod
def get_albums_by_artisthashes(cls, hashes: Iterable[str]):
"""
Returns all albums by the given artist hashes.
"""
albums = []
for hash in hashes:
albums.extend(cls.get_albums_by_artisthash(hash))
return albums
@classmethod
def get_album_tracks(cls, albumhash: str) -> list[Track]:
"""
Returns all tracks for the given album hash.
"""
album = cls.albummap.get(albumhash)
if not album:
return []
return TrackStore.get_tracks_by_trackhashes(album.trackhashes)
+46 -16
View File
@@ -1,19 +1,30 @@
import json import json
from typing import Iterable
from app.db.sqlite.artistcolors import SQLiteArtistMethods as ardb from app.db.sqlite.artistcolors import SQLiteArtistMethods as ardb
from app.lib.tagger import create_artists
from app.models import Artist from app.models import Artist
from app.utils.bisection import use_bisection from app.utils.bisection import use_bisection
from app.utils.customlist import CustomList from app.utils.customlist import CustomList
from app.utils.progressbar import tqdm from app.utils.progressbar import tqdm
from .tracks import TrackStore
from .albums import AlbumStore # from .albums import AlbumStore
from .tracks import TrackStore from .tracks import TrackStore
ARTIST_LOAD_KEY = "" ARTIST_LOAD_KEY = ""
class ArtistMapEntry:
def __init__(self, artist: Artist) -> None:
self.artist = artist
self.albumhashes: set[str] = set()
self.trackhashes: set[str] = set()
class ArtistStore: class ArtistStore:
artists: list[Artist] = CustomList() artists: list[Artist] = CustomList()
artistmap: dict[str, ArtistMapEntry] = {}
@classmethod @classmethod
def load_artists(cls, instance_key: str): def load_artists(cls, instance_key: str):
@@ -24,15 +35,27 @@ class ArtistStore:
ARTIST_LOAD_KEY = instance_key ARTIST_LOAD_KEY = instance_key
print("Loading artists... ", end="") print("Loading artists... ", end="")
cls.artists.clear() cls.artistmap.clear()
cls.artists.extend(get_all_artists(TrackStore.tracks, AlbumStore.albums)) cls.artistmap = {
print("Done!") artist.artisthash: ArtistMapEntry(artist=artist)
for artist in ardb.get_all_artists(): for artist in create_artists()
}
for track in TrackStore.get_flat_list():
if instance_key != ARTIST_LOAD_KEY: if instance_key != ARTIST_LOAD_KEY:
return return
cls.map_artist_color(artist) for hash in track.artisthashes:
cls.artistmap[hash].trackhashes.add(track.trackhash)
cls.artistmap[hash].albumhashes.add(track.albumhash)
print("Done!")
# for artist in ardb.get_all_artists():
# if instance_key != ARTIST_LOAD_KEY:
# return
# cls.map_artist_color(artist)
@classmethod @classmethod
def map_artist_color(cls, artist_tuple: tuple): def map_artist_color(cls, artist_tuple: tuple):
@@ -65,24 +88,20 @@ class ArtistStore:
cls.artists.append(artist) cls.artists.append(artist)
@classmethod @classmethod
def get_artist_by_hash(cls, artisthash: str) -> Artist: def get_artist_by_hash(cls, artisthash: str):
""" """
Returns an artist by its hash.P Returns an artist by its hash.P
""" """
artists = sorted(cls.artists, key=lambda x: x.artisthash) entry = cls.artistmap.get(artisthash, None)
try: if entry is not None:
artist = use_bisection(artists, "artisthash", [artisthash])[0] return entry.artist
return artist
except IndexError:
return None
@classmethod @classmethod
def get_artists_by_hashes(cls, artisthashes: list[str]) -> list[Artist]: def get_artists_by_hashes(cls, artisthashes: Iterable[str]):
""" """
Returns artists by their hashes. Returns artists by their hashes.
""" """
artists = sorted(cls.artists, key=lambda x: x.artisthash) artists = [cls.get_artist_by_hash(hash) for hash in artisthashes]
artists = use_bisection(artists, "artisthash", artisthashes)
return [a for a in artists if a is not None] return [a for a in artists if a is not None]
@classmethod @classmethod
@@ -113,3 +132,14 @@ class ArtistStore:
Removes an artist from the store. Removes an artist from the store.
""" """
cls.artists = CustomList(a for a in cls.artists if a.artisthash != artisthash) cls.artists = CustomList(a for a in cls.artists if a.artisthash != artisthash)
@classmethod
def get_artist_tracks(cls, artisthash: str):
"""
Returns all tracks by the given artist hash.
"""
entry = cls.artistmap.get(artisthash)
if entry is not None:
return TrackStore.get_tracks_by_trackhashes(entry.trackhashes)
return []
+5 -4
View File
@@ -1,8 +1,7 @@
# from tqdm import tqdm # from tqdm import tqdm
import itertools import itertools
import sys from typing import Callable, Iterable
from typing import Callable
from flask_jwt_extended import current_user from flask_jwt_extended import current_user
from app.db.libdata import TrackTable from app.db.libdata import TrackTable
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
@@ -243,7 +242,7 @@ class TrackStore:
# ================================================ # ================================================
@classmethod @classmethod
def get_tracks_by_trackhashes(cls, trackhashes: list[str]) -> list[Track]: def get_tracks_by_trackhashes(cls, trackhashes: Iterable[str]) -> list[Track]:
""" """
Returns a list of tracks by their hashes. Returns a list of tracks by their hashes.
""" """
@@ -259,7 +258,9 @@ class TrackStore:
tracks.append(track) tracks.append(track)
# sort the tracks in the order of the given trackhashes # sort the tracks in the order of the given trackhashes
tracks.sort(key=lambda t: trackhashes.index(t.trackhash)) if type(trackhashes) == list:
tracks.sort(key=lambda t: trackhashes.index(t.trackhash))
return tracks return tracks
@classmethod @classmethod