mirror of
https://github.com/tcsenpai/swingmusic.git
synced 2025-06-06 03:05:35 +00:00
add routes to get all albums and artists with sort
+ rewrite load all albums + artist logic with itertools.groupby + add a function to convert seconds to string
This commit is contained in:
parent
7f87cde96c
commit
336360d509
@ -22,6 +22,7 @@ from app.api import (
|
|||||||
plugins,
|
plugins,
|
||||||
logger,
|
logger,
|
||||||
home,
|
home,
|
||||||
|
getall,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -60,4 +61,7 @@ def create_api():
|
|||||||
# Home
|
# Home
|
||||||
app.register_blueprint(home.api_bp)
|
app.register_blueprint(home.api_bp)
|
||||||
|
|
||||||
|
# Flask Restful
|
||||||
|
app.register_blueprint(getall.api_bp)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
@ -67,11 +67,7 @@ def get_album_tracks_and_info():
|
|||||||
album.count = len(tracks)
|
album.count = len(tracks)
|
||||||
|
|
||||||
album.get_date_from_tracks(tracks)
|
album.get_date_from_tracks(tracks)
|
||||||
|
album.duration = sum(t.duration for t in tracks)
|
||||||
try:
|
|
||||||
album.duration = sum(t.duration for t in tracks)
|
|
||||||
except AttributeError:
|
|
||||||
album.duration = 0
|
|
||||||
|
|
||||||
album.check_is_single(tracks)
|
album.check_is_single(tracks)
|
||||||
|
|
||||||
|
10
app/api/getall/__init__.py
Normal file
10
app/api/getall/__init__.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
from flask_restful import Api
|
||||||
|
|
||||||
|
from .resources import Albums
|
||||||
|
|
||||||
|
api_bp = Blueprint("getall", __name__, url_prefix="/getall")
|
||||||
|
api = Api(api_bp)
|
||||||
|
|
||||||
|
|
||||||
|
api.add_resource(Albums, "/<itemtype>")
|
93
app/api/getall/resources.py
Normal file
93
app/api/getall/resources.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
from flask_restful import Resource, reqparse
|
||||||
|
from datetime import datetime
|
||||||
|
from app.store.albums import AlbumStore
|
||||||
|
from app.store.artists import ArtistStore
|
||||||
|
|
||||||
|
from app.serializers.album import serialize_for_card as serialize_album
|
||||||
|
from app.serializers.artist import serialize_for_card as serialize_artist
|
||||||
|
from app.utils import format_number
|
||||||
|
from app.utils.dates import (
|
||||||
|
create_new_date,
|
||||||
|
date_string_to_time_passed,
|
||||||
|
seconds_to_time_string,
|
||||||
|
)
|
||||||
|
|
||||||
|
parser = reqparse.RequestParser()
|
||||||
|
|
||||||
|
parser.add_argument("start", type=int, default=0, location="args")
|
||||||
|
parser.add_argument("limit", type=int, default=20, location="args")
|
||||||
|
parser.add_argument("sortby", type=str, default="created_date", location="args")
|
||||||
|
parser.add_argument("reverse", type=str, default="1", location="args")
|
||||||
|
|
||||||
|
|
||||||
|
class Albums(Resource):
|
||||||
|
def get(self, itemtype: str):
|
||||||
|
is_albums = itemtype == "albums"
|
||||||
|
is_artists = itemtype == "artists"
|
||||||
|
|
||||||
|
items = AlbumStore.albums
|
||||||
|
|
||||||
|
if is_artists:
|
||||||
|
items = ArtistStore.artists
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
start = args["start"]
|
||||||
|
limit = args["limit"]
|
||||||
|
sort = args["sortby"]
|
||||||
|
reverse = args["reverse"] == "1"
|
||||||
|
|
||||||
|
if sort == "":
|
||||||
|
sort = "created_date"
|
||||||
|
|
||||||
|
sort_is_count = sort == "count"
|
||||||
|
sort_is_duration = sort == "duration"
|
||||||
|
|
||||||
|
sort_is_date = is_albums and sort == "date"
|
||||||
|
sort_is_create_date = is_albums and sort == "created_date"
|
||||||
|
sort_is_artist = is_albums and sort == "albumartists"
|
||||||
|
|
||||||
|
sort_is_artist_trackcount = is_artists and sort == "trackcount"
|
||||||
|
sort_is_artist_albumcount = is_artists and sort == "albumcount"
|
||||||
|
|
||||||
|
lambda_sort = lambda x: getattr(x, sort)
|
||||||
|
if sort_is_artist:
|
||||||
|
lambda_sort = lambda x: getattr(x, sort)[0].name
|
||||||
|
|
||||||
|
sorted_items = sorted(items, key=lambda_sort, reverse=reverse)
|
||||||
|
items = sorted_items[start : start + limit]
|
||||||
|
|
||||||
|
album_list = []
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
item_dict = serialize_album(item) if is_albums else serialize_artist(item)
|
||||||
|
|
||||||
|
if sort_is_date:
|
||||||
|
item_dict["help_text"] = item.date
|
||||||
|
|
||||||
|
if sort_is_create_date:
|
||||||
|
date = create_new_date(datetime.fromtimestamp(item.created_date))
|
||||||
|
timeago = date_string_to_time_passed(date)
|
||||||
|
item_dict["help_text"] = timeago
|
||||||
|
|
||||||
|
if sort_is_count:
|
||||||
|
item_dict[
|
||||||
|
"help_text"
|
||||||
|
] = f"{format_number(item.count)} track{'' if item.count == 1 else 's'}"
|
||||||
|
|
||||||
|
if sort_is_duration:
|
||||||
|
item_dict["help_text"] = seconds_to_time_string(item.duration)
|
||||||
|
|
||||||
|
if sort_is_artist_trackcount:
|
||||||
|
item_dict[
|
||||||
|
"help_text"
|
||||||
|
] = f"{format_number(item.trackcount)} track{'' if item.trackcount == 1 else 's'}"
|
||||||
|
|
||||||
|
if sort_is_artist_albumcount:
|
||||||
|
item_dict[
|
||||||
|
"help_text"
|
||||||
|
] = f"{format_number(item.albumcount)} album{'' if item.albumcount == 1 else 's'}"
|
||||||
|
|
||||||
|
album_list.append(item_dict)
|
||||||
|
|
||||||
|
return {"items": album_list, "total": len(sorted_items)}
|
@ -4,14 +4,41 @@ Contains methods relating to albums.
|
|||||||
|
|
||||||
from dataclasses import asdict
|
from dataclasses import asdict
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from itertools import groupby
|
||||||
|
|
||||||
|
|
||||||
from app.logger import log
|
|
||||||
from app.models.track import Track
|
from app.models.track import Track
|
||||||
from app.store.albums import AlbumStore
|
from app.store.albums import AlbumStore
|
||||||
from app.store.tracks import TrackStore
|
from app.store.tracks import TrackStore
|
||||||
|
|
||||||
|
|
||||||
|
def create_albums():
|
||||||
|
"""
|
||||||
|
Creates albums from the tracks in the store.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# group all tracks by albumhash
|
||||||
|
tracks = TrackStore.tracks
|
||||||
|
tracks = sorted(tracks, key=lambda t: t.albumhash)
|
||||||
|
grouped = groupby(tracks, lambda t: t.albumhash)
|
||||||
|
|
||||||
|
# create albums from the groups
|
||||||
|
albums: list[Track] = []
|
||||||
|
for albumhash, tracks in grouped:
|
||||||
|
count = len(list(tracks))
|
||||||
|
duration = sum(t.duration for t in tracks)
|
||||||
|
created_date = min(t.created_date for t in tracks)
|
||||||
|
|
||||||
|
album = AlbumStore.create_album(list(tracks)[0])
|
||||||
|
album.set_count(count)
|
||||||
|
album.set_duration(duration)
|
||||||
|
album.set_created_date(created_date)
|
||||||
|
|
||||||
|
albums.append(album)
|
||||||
|
|
||||||
|
return albums
|
||||||
|
|
||||||
|
|
||||||
def validate_albums():
|
def validate_albums():
|
||||||
"""
|
"""
|
||||||
Removes albums that have no tracks.
|
Removes albums that have no tracks.
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from collections import namedtuple
|
||||||
|
from itertools import groupby
|
||||||
import os
|
import os
|
||||||
import urllib
|
import urllib
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
@ -12,6 +14,7 @@ from requests.exceptions import ReadTimeout
|
|||||||
from app import settings
|
from app import settings
|
||||||
from app.models import Album, Artist, Track
|
from app.models import Album, Artist, Track
|
||||||
from app.store import artists as artist_store
|
from app.store import artists as artist_store
|
||||||
|
from app.store.tracks import TrackStore
|
||||||
from app.utils.hashing import create_hash
|
from app.utils.hashing import create_hash
|
||||||
from app.utils.progressbar import tqdm
|
from app.utils.progressbar import tqdm
|
||||||
|
|
||||||
@ -190,21 +193,63 @@ def get_albumartists(albums: list[Album]) -> set[str]:
|
|||||||
|
|
||||||
|
|
||||||
def get_all_artists(tracks: list[Track], albums: list[Album]) -> list[Artist]:
|
def get_all_artists(tracks: list[Track], albums: list[Album]) -> list[Artist]:
|
||||||
artists_from_tracks = get_artists_from_tracks(tracks=tracks)
|
TrackInfo = namedtuple(
|
||||||
artist_from_albums = get_albumartists(albums=albums)
|
"TrackInfo",
|
||||||
|
[
|
||||||
|
"artisthash",
|
||||||
|
"albumhash",
|
||||||
|
"trackhash",
|
||||||
|
"duration",
|
||||||
|
"artistname",
|
||||||
|
"created_date",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
src_tracks = TrackStore.tracks
|
||||||
|
all_tracks: set[TrackInfo] = set()
|
||||||
|
|
||||||
artists = list(artists_from_tracks.union(artist_from_albums))
|
for track in src_tracks:
|
||||||
artists.sort()
|
artist_hashes = {(a.name, a.artisthash) for a in track.artists}.union(
|
||||||
|
(a.name, a.artisthash) for a in track.albumartists
|
||||||
|
)
|
||||||
|
|
||||||
# Remove duplicates
|
for artist in artist_hashes:
|
||||||
artists_dup_free = set()
|
track_info = TrackInfo(
|
||||||
artist_hashes = set()
|
artistname=artist[0],
|
||||||
|
artisthash=artist[1],
|
||||||
|
albumhash=track.albumhash,
|
||||||
|
trackhash=track.trackhash,
|
||||||
|
duration=track.duration,
|
||||||
|
created_date=track.created_date,
|
||||||
|
# work on created date
|
||||||
|
)
|
||||||
|
|
||||||
for artist in artists:
|
all_tracks.add(track_info)
|
||||||
artist_hash = create_hash(artist, decode=True)
|
|
||||||
|
|
||||||
if artist_hash not in artist_hashes:
|
all_tracks = sorted(all_tracks, key=lambda x: x.artisthash)
|
||||||
artists_dup_free.add(artist)
|
all_tracks = groupby(all_tracks, key=lambda x: x.artisthash)
|
||||||
artist_hashes.add(artist_hash)
|
|
||||||
|
|
||||||
return [Artist(a) for a in artists_dup_free]
|
artists = []
|
||||||
|
|
||||||
|
for artisthash, tracks in all_tracks:
|
||||||
|
tracks: list[TrackInfo] = list(tracks)
|
||||||
|
|
||||||
|
artistname = (
|
||||||
|
sorted({t.artistname for t in tracks})[0]
|
||||||
|
if len(tracks) > 1
|
||||||
|
else tracks[0].artistname
|
||||||
|
)
|
||||||
|
|
||||||
|
albumcount = len({t.albumhash for t in tracks})
|
||||||
|
duration = sum(t.duration for t in tracks)
|
||||||
|
created_date = min(t.created_date for t in tracks)
|
||||||
|
|
||||||
|
artist = Artist(name=artistname)
|
||||||
|
|
||||||
|
artist.set_trackcount(len(tracks))
|
||||||
|
artist.set_albumcount(albumcount)
|
||||||
|
artist.set_duration(duration)
|
||||||
|
artist.set_created_date(created_date)
|
||||||
|
|
||||||
|
artists.append(artist)
|
||||||
|
|
||||||
|
return artists
|
||||||
|
@ -27,6 +27,7 @@ class Album:
|
|||||||
colors: list[str] = dataclasses.field(default_factory=list)
|
colors: list[str] = dataclasses.field(default_factory=list)
|
||||||
date: str = ""
|
date: str = ""
|
||||||
|
|
||||||
|
created_date: int = 0
|
||||||
og_title: str = ""
|
og_title: str = ""
|
||||||
base_title: str = ""
|
base_title: str = ""
|
||||||
is_soundtrack: bool = False
|
is_soundtrack: bool = False
|
||||||
@ -40,6 +41,7 @@ class Album:
|
|||||||
versions: list[str] = dataclasses.field(default_factory=list)
|
versions: list[str] = dataclasses.field(default_factory=list)
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
|
self.title = self.title.strip()
|
||||||
self.og_title = self.title
|
self.og_title = self.title
|
||||||
self.image = self.albumhash + ".webp"
|
self.image = self.albumhash + ".webp"
|
||||||
|
|
||||||
@ -202,3 +204,12 @@ class Album:
|
|||||||
|
|
||||||
dates = (int(t.date) for t in tracks if t.date)
|
dates = (int(t.date) for t in tracks if t.date)
|
||||||
self.date = datetime.datetime.fromtimestamp(min(dates)).year
|
self.date = datetime.datetime.fromtimestamp(min(dates)).year
|
||||||
|
|
||||||
|
def set_count(self, count: int):
|
||||||
|
self.count = count
|
||||||
|
|
||||||
|
def set_duration(self, duration: int):
|
||||||
|
self.duration = duration
|
||||||
|
|
||||||
|
def set_created_date(self, created_date: int):
|
||||||
|
self.created_date = created_date
|
||||||
|
@ -36,6 +36,7 @@ class Artist(ArtistMinimal):
|
|||||||
duration: int = 0
|
duration: int = 0
|
||||||
colors: list[str] = dataclasses.field(default_factory=list)
|
colors: list[str] = dataclasses.field(default_factory=list)
|
||||||
is_favorite: bool = False
|
is_favorite: bool = False
|
||||||
|
created_date: float = 0.0
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
super(Artist, self).__init__(self.name)
|
super(Artist, self).__init__(self.name)
|
||||||
@ -51,3 +52,6 @@ class Artist(ArtistMinimal):
|
|||||||
|
|
||||||
def set_colors(self, colors: list[str]):
|
def set_colors(self, colors: list[str]):
|
||||||
self.colors = colors
|
self.colors = colors
|
||||||
|
|
||||||
|
def set_created_date(self, created_date: float):
|
||||||
|
self.created_date = created_date
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
|
from itertools import groupby
|
||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
|
||||||
from app.db.sqlite.albumcolors import SQLiteAlbumMethods as aldb
|
from app.db.sqlite.albumcolors import SQLiteAlbumMethods as aldb
|
||||||
from app.models import Album, Track
|
from app.models import Album, Track
|
||||||
|
from app.utils.remove_duplicates import remove_duplicates
|
||||||
|
|
||||||
from ..utils.hashing import create_hash
|
from ..utils.hashing import create_hash
|
||||||
from .tracks import TrackStore
|
from .tracks import TrackStore
|
||||||
@ -36,16 +37,29 @@ class AlbumStore:
|
|||||||
|
|
||||||
cls.albums = []
|
cls.albums = []
|
||||||
|
|
||||||
albumhashes = set(t.albumhash for t in TrackStore.tracks)
|
tracks = remove_duplicates(TrackStore.tracks)
|
||||||
|
tracks = sorted(tracks, key=lambda t: t.albumhash)
|
||||||
|
grouped = groupby(tracks, lambda t: t.albumhash)
|
||||||
|
|
||||||
for albumhash in tqdm(albumhashes, desc=f"Loading albums"):
|
for albumhash, tracks in grouped:
|
||||||
if instance_key != ALBUM_LOAD_KEY:
|
tracks = list(tracks)
|
||||||
return
|
sample = tracks[0]
|
||||||
|
|
||||||
for track in TrackStore.tracks:
|
if sample is None:
|
||||||
if track.albumhash == albumhash:
|
continue
|
||||||
cls.albums.append(cls.create_album(track))
|
|
||||||
break
|
count = len(list(tracks))
|
||||||
|
duration = sum(t.duration for t in tracks)
|
||||||
|
created_date = min(t.created_date for t in tracks)
|
||||||
|
|
||||||
|
album = AlbumStore.create_album(sample)
|
||||||
|
|
||||||
|
album.get_date_from_tracks(tracks)
|
||||||
|
album.set_count(count)
|
||||||
|
album.set_duration(duration)
|
||||||
|
album.set_created_date(created_date)
|
||||||
|
|
||||||
|
cls.albums.append(album)
|
||||||
|
|
||||||
db_albums: list[tuple] = aldb.get_all_albums()
|
db_albums: list[tuple] = aldb.get_all_albums()
|
||||||
|
|
||||||
|
@ -23,8 +23,9 @@ class ArtistStore:
|
|||||||
global ARTIST_LOAD_KEY
|
global ARTIST_LOAD_KEY
|
||||||
ARTIST_LOAD_KEY = instance_key
|
ARTIST_LOAD_KEY = instance_key
|
||||||
|
|
||||||
|
print("Loading artists... ", end=" ")
|
||||||
cls.artists = get_all_artists(TrackStore.tracks, AlbumStore.albums)
|
cls.artists = get_all_artists(TrackStore.tracks, AlbumStore.albums)
|
||||||
|
print("Done!")
|
||||||
for artist in ardb.get_all_artists():
|
for artist in ardb.get_all_artists():
|
||||||
if instance_key != ARTIST_LOAD_KEY:
|
if instance_key != ARTIST_LOAD_KEY:
|
||||||
return
|
return
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
import locale
|
||||||
|
|
||||||
|
# Set to user's default locale:
|
||||||
|
locale.setlocale(locale.LC_ALL, "")
|
||||||
|
|
||||||
|
# Or set to a specific locale:
|
||||||
|
# locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
|
||||||
|
|
||||||
|
|
||||||
|
def format_number(number: float) -> str:
|
||||||
|
return locale.format_string("%d", number, grouping=True)
|
@ -25,3 +25,23 @@ def date_string_to_time_passed(prev_date: str) -> str:
|
|||||||
diff = now - then
|
diff = now - then
|
||||||
now = pendulum.now()
|
now = pendulum.now()
|
||||||
return now.subtract(seconds=diff).diff_for_humans()
|
return now.subtract(seconds=diff).diff_for_humans()
|
||||||
|
|
||||||
|
|
||||||
|
def seconds_to_time_string(seconds):
|
||||||
|
"""
|
||||||
|
Converts seconds to a time string. e.g. 1 hour 2 minutes, 1 hour 2 seconds, 1 hour, 1 minute 2 seconds, etc.
|
||||||
|
"""
|
||||||
|
hours = seconds // 3600
|
||||||
|
minutes = (seconds % 3600) // 60
|
||||||
|
remaining_seconds = seconds % 60
|
||||||
|
|
||||||
|
if hours > 0:
|
||||||
|
if minutes > 0:
|
||||||
|
return f"{hours} hr{'s' if hours > 1 else ''}, {minutes} minute{'s' if minutes > 1 else ''}"
|
||||||
|
|
||||||
|
return f"{hours} hr{'s' if hours > 1 else ''}"
|
||||||
|
|
||||||
|
if minutes > 0:
|
||||||
|
return f"{minutes} minute{'s' if minutes > 1 else ''}"
|
||||||
|
|
||||||
|
return f"{remaining_seconds} second{'s' if remaining_seconds > 1 else ''}"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user