add track mixes

This commit is contained in:
cwilvx
2024-11-27 12:35:48 +03:00
parent 809415ddb4
commit ee0f6c7646
5 changed files with 116 additions and 20 deletions
+2
View File
@@ -53,6 +53,8 @@ def get_mix(query: MixQuery):
match query.mixid[0]:
case "a":
mixtype = "artist_mixes"
case "t":
mixtype = "custom_mixes"
case _:
return {"msg": "Invalid mix ID"}, 400
+15 -1
View File
@@ -23,5 +23,19 @@ class ArtistMixes(HomepageRoutine):
HomepageStore.set_mixes(mixes, entrykey=self.store_key, userid=user.id)
custom_mixes = []
for _mix in mixes:
custom_mix = mix.get_custom_mix_items(_mix)
if custom_mix:
custom_mixes.append(custom_mix)
for index, custom_mix in enumerate(custom_mixes):
custom_mix.title = f"Mix {index + 1}"
HomepageStore.set_mixes(
custom_mixes, entrykey="custom_mixes", userid=user.id
)
def __init__(self) -> None:
super().__init__()
super().__init__()
+2 -2
View File
@@ -30,8 +30,8 @@ class Mix:
_dict = asdict(self)
_dict["tracks"] = serialized_tracks
if not self.extra.get("image"):
_dict["images"] = get_first_4_images(tracks)
# if not self.extra.get("image"):
# _dict["images"] = get_first_4_images(tracks)
_dict["duration"] = seconds_to_time_string(sum(t.duration for t in tracks))
_dict["trackcount"] = len(tracks)
+93 -14
View File
@@ -1,16 +1,13 @@
import datetime
from gettext import ngettext
import json
from pprint import pprint
import random
import string
import time
import requests
from urllib.parse import quote
from PIL import Image
from app.db.userdata import MixTable, SimilarArtistTable
from app.db.userdata import MixTable
from app.lib.colorlib import get_image_colors
from app.lib.playlistlib import get_first_4_images
from app.models.artist import Artist
from app.models.mix import Mix
from app.models.track import Track
@@ -22,7 +19,6 @@ from app.store.tracks import TrackStore
from app.utils.dates import get_date_range, get_duration_ago
from app.utils.hashing import create_hash
from app.utils.mixes import balance_mix
from app.utils.remove_duplicates import remove_duplicates
from app.utils.stats import get_artists_in_period
@@ -36,8 +32,8 @@ class MixAlreadyExists(Exception):
class MixesPlugin(Plugin):
MAX_TRACKS_TO_FETCH = 5
TRACK_MIX_LENGTH = 30
MIN_TRACK_MIX_LENGTH = 15
MIX_TRACKS = 40
MIN_DAY_LISTEN_DURATION = 3 * 60 # 3 minutes
MIN_WEEK_LISTEN_DURATION = 10 * 60 # 10 minutes
@@ -421,16 +417,76 @@ class MixesPlugin(Plugin):
"""
pass
def get_custom_mix_items(self, mix: Mix):
"""
Given a mix, returns the excess tracks as a custom mix.
"""
# INFO: If the mix can't have more than 20 tracks, return None
if len(mix.tracks) <= self.MIX_TRACKS + 20:
return None
tracks = TrackStore.get_tracks_by_trackhashes(mix.tracks[self.MIX_TRACKS :])
return Mix(
id=f"t{mix.extra['artisthash']}",
title="", # INFO: Will be filled after all mixes are created.
description=self.get_mix_description(tracks, mix.extra["artisthash"]),
tracks=[t.trackhash for t in tracks],
sourcehash=create_hash(*[t.trackhash for t in tracks]),
extra={
"type": "track",
"images": self.get_custom_mix_images(tracks),
"artists": None,
"albums": None,
},
)
def get_custom_mix_images(self, tracks: list[Track]):
first_album = tracks[0].albumhash
first_img = {
"image": first_album + ".webp",
"type": "album",
"color": AlbumStore.albummap[first_album].album.color,
}
seen = set()
images = [first_img]
for track in tracks[1:]:
artisthash = track.artists[0]["artisthash"]
if artisthash in seen:
continue
seen.add(artisthash)
image = {
"image": artisthash + ".webp",
"type": "artist",
"color": ArtistStore.get_artist_by_hash(artisthash).color,
}
images.append(image)
if len(images) == 3:
break
return images
def get_because_items(self, mixes: list[Mix]):
"""
Given a list of mixes, returns a list of artists that are similar to the
artists in the mixes.
"""
artists: dict[str, list[dict[str, str | int]]] = {}
albums: dict[str, list[dict[str, str | int]]] = {}
for mix in mixes:
mix_artisthash = mix.extra["artisthash"]
artists.setdefault(mix_artisthash, [])
albums.setdefault(mix_artisthash, [])
for artisthash in mix.extra["artists"]:
artist = ArtistStore.artistmap.get(artisthash)
@@ -448,6 +504,22 @@ class MixesPlugin(Plugin):
}
)
for albumhash in mix.extra["albums"]:
album = AlbumStore.albummap.get(albumhash)
if not album:
continue
albums[mix_artisthash].append(
{
"type": "album",
"trackcount": album.album.trackcount,
"hash": albumhash,
"help_text": str(album.album.trackcount)
+ ngettext(" track", " tracks", album.album.trackcount),
}
)
# INFO: Sort artists by trackcount
artists[mix_artisthash] = sorted(
artists[mix_artisthash],
@@ -455,26 +527,33 @@ class MixesPlugin(Plugin):
reverse=True,
)
# INFO: Sort albums by trackcount
albums[mix_artisthash] = sorted(
albums[mix_artisthash],
key=lambda x: x["trackcount"],
reverse=True,
)
artisthash = mixes[0].extra["artisthash"]
because_you_listened_to_artist = {
"title": "Because you listened to "
+ ArtistStore.artistmap[artisthash].artist.name,
"items": artists[artisthash][:15],
"items": albums[artisthash][:15],
}
# Flatten list of artists and remove duplicates by artisthash
all_artists = []
seen = set()
for artist_list in artists.values():
for artist in artist_list:
if artist["hash"] not in seen:
all_artists.append(artist)
seen.add(artist["hash"])
# for artist_list in artists.values():
# for artist in artist_list:
# if artist["hash"] not in seen:
# all_artists.append(artist)
# seen.add(artist["hash"])
artists_you_might_like = {
"title": "Artists you might like",
"items": random.sample(all_artists, k=min(15, len(all_artists))),
"items": artists[artisthash][:15],
}
return because_you_listened_to_artist, artists_you_might_like
+4 -3
View File
@@ -1,6 +1,4 @@
from abc import ABC
from dataclasses import dataclass
import pprint
from typing import Any
from app.lib.home.recentlyplayed import recover_items
from app.models.mix import Mix
@@ -111,7 +109,6 @@ class BecauseYouListenedToArtistHomepageEntry(RecentlyPlayedHomepageEntry):
items: dict[int, dict[str, Any]]
def get_items(self, userid: int, limit: int | None = None):
pprint.pprint(self.items)
title = self.items.get(userid, {}).get("title")
items = self.items.get(userid, {}).get("items", [])[:limit]
@@ -134,6 +131,10 @@ class HomepageStore:
title="Artist mixes for you",
description="Based on artists you have been listening to",
),
"custom_mixes": MixHomepageEntry(
title="Mixes for you",
description="Because artist mixes alone aren't enough",
),
"top_streamed_weekly_artists": GenericRecoverableEntry(
title="Top artists this week",
description="Your most played artists since Monday",