document and add image to mix

This commit is contained in:
cwilvx
2024-10-28 16:42:51 +03:00
parent f2153d936d
commit f6373292aa
5 changed files with 91 additions and 29 deletions
+2 -2
View File
@@ -31,7 +31,7 @@ def get_track_mix():
@api.post("/artist")
def get_artist_mix():
mixes = MixesPlugin()
return mixes.get_artists()
return mixes.create_artist_mixes()
# tracks = mixes.get_artist_mix("09306be8039b98ad")
# return {
@@ -54,7 +54,7 @@ def get_mix(query: MixQuery):
case "a":
mixtype = "artist_mixes"
case _:
raise ValueError(f"Invalid mix ID: {query.mixid}")
return {"msg": "Invalid mix ID"}, 400
mix = HomepageStore.get_mix(mixtype, query.mixid[1:])
if mix:
+1 -1
View File
@@ -16,6 +16,6 @@ class Mixes(CronJob):
"""
print("⭐⭐⭐⭐ Mixes cron job running")
mixes = MixesPlugin()
artist_mixes = mixes.get_artists()
artist_mixes = mixes.create_artist_mixes()
HomepageStore.set_artist_mixes(artist_mixes)
+1
View File
@@ -13,6 +13,7 @@ class Mix:
title: str
description: str
tracks: list[str]
image: dict
timestamp: int = field(default_factory=lambda: int(time.time()))
extra: dict = field(default_factory=dict)
+49 -6
View File
@@ -32,7 +32,19 @@ class MixesPlugin(Plugin):
@plugin_method
def get_track_mix(self, tracks: list[Track], with_help: bool = False):
# query = f"{track.title} - {','.join(a['name'] for a in track.artists)}"
"""
Given a list of tracks, creates a mix by fetching data from the
Swing Music Cloud recommendation server.
The server returns a list of weak trackhashes. We use these to fetch
the matching track data from our library database. Found tracks are
then balanced and returned as the final mix tracklist.
:param with_help: Whether to include the help flag in the query.
The flag tells the server to find more data using other tracks from the same album.
"""
queries = [
{
"query": f"{track.title} - {','.join(a['name'] for a in track.artists)}",
@@ -60,15 +72,23 @@ class MixesPlugin(Plugin):
for track in trackmatches:
grouped.setdefault(track.weakhash, []).append(track)
trackmatches = [max(group, key=lambda x: x.bitrate) for group in grouped.values()]
trackmatches = [
max(group, key=lambda x: x.bitrate) for group in grouped.values()
]
# sort by trackhash order
trackmatches = sorted(trackmatches, key=lambda x: trackhashes.index(x.weakhash))
# try to balance the mix
trackmatches = balance_mix(trackmatches)
return trackmatches
@plugin_method
def get_artist_mix(self, artisthash: str):
"""
Given an artisthash, creates an artist mix using the
self.MAX_TRACKS_TO_FETCH most listened to tracks.
"""
artist = ArtistStore.artistmap[artisthash]
tracks = TrackStore.get_tracks_by_trackhashes(artist.trackhashes)
@@ -76,7 +96,7 @@ class MixesPlugin(Plugin):
return self.get_track_mix(tracks[: self.MAX_TRACKS_TO_FETCH])
@plugin_method
def get_artists(self, limit: int = 10):
def create_artist_mixes(self, limit: int = 10):
mixes: list[Mix] = []
indexed = set()
@@ -130,6 +150,10 @@ class MixesPlugin(Plugin):
return mixes
def get_mix_description(self, tracks: list[Track], artishash: str):
"""
Constructs a description for a mix by putting together the first n=4
artists in the mix tracklist.
"""
first_4_artists = []
indexed = set()
@@ -151,13 +175,22 @@ class MixesPlugin(Plugin):
return f"Featuring {tracks[0].artists[0]['name']}"
def create_artist_mix(self, artist: dict[str, str]):
"""
Given an artist dict, creates an artist mix.
"""
mix_tracks = self.get_artist_mix(artist["artisthash"])
if len(mix_tracks) < self.MIN_TRACK_MIX_LENGTH:
return None
_artist = ArtistStore.get_artist_by_hash(artist["artisthash"])
if not _artist:
return None
return Mix(
id=artist["artisthash"],
# the a prefix indicates that this is an artist mix
id=f"a{artist['artisthash']}",
title=artist["artist"],
description=self.get_mix_description(mix_tracks, artist["artisthash"]),
tracks=[t.trackhash for t in mix_tracks],
@@ -165,10 +198,19 @@ class MixesPlugin(Plugin):
"type": "artist",
"artisthash": artist["artisthash"],
},
image={
"image": _artist.image,
"color": _artist.color,
},
)
def fallback_create_artist_mix(self, artist: dict[str, str], similar_artists: list[str], trackhashes: set[str], limit: int):
def fallback_create_artist_mix(
self,
artist: dict[str, str],
similar_artists: list[str],
trackhashes: set[str],
limit: int,
):
"""
Creates an artist mix by selecting random tracks from similar artists.
@@ -201,3 +243,4 @@ class MixesPlugin(Plugin):
# Since the artisthashes are ordered by similarity score, we iterate from the start
# and go forward collecting tracks that aren't in the mix yet.
#
+25 -7
View File
@@ -3,20 +3,31 @@ from typing import List, Dict, Tuple
from collections import Counter
def violates_gap_rule(balanced_mix: Dict[int, Track], position: int, track: Track, gap: int = 3) -> bool:
def violates_gap_rule(
balanced_mix: Dict[int, Track], position: int, track: Track, gap: int = 3
) -> bool:
"""
Check if placing the track at the given position violates the gap rule.
The gap rule is violated if the track has an artist in common with any
track within the gap range (default = 3).
"""
track_artists = set(artist['artisthash'] for artist in track.artists)
track_artists = set(artist["artisthash"] for artist in track.artists)
for i in range(max(0, position - gap), position):
if i in balanced_mix:
existing_artists = set(artist['artisthash'] for artist in balanced_mix[i].artists)
existing_artists = set(
artist["artisthash"] for artist in balanced_mix[i].artists
)
if track_artists.intersection(existing_artists):
return True
return False
def find_next_position(balanced_mix: Dict[int, Track], start: int, track: Track, total_tracks: int) -> int:
def find_next_position(
balanced_mix: Dict[int, Track], start: int, track: Track, total_tracks: int
) -> int:
"""
Find the next available position for the track, starting from 'start' and wrapping around.
"""
@@ -28,6 +39,7 @@ def find_next_position(balanced_mix: Dict[int, Track], start: int, track: Track,
return i
return start # If no better position is found, return the original position
def is_tracklist_balanced(tracks: List[Track], gap: int = 3) -> Tuple[bool, bool]:
"""
Checks if a tracklist is balanced or can be balanced.
@@ -42,13 +54,15 @@ def is_tracklist_balanced(tracks: List[Track], gap: int = 3) -> Tuple[bool, bool
total_tracks = len(tracks)
# Count tracks per artist (considering only the first artist)
artist_counts = Counter(track.artists[0]['artisthash'] for track in tracks)
artist_counts = Counter(track.artists[0]["artisthash"] for track in tracks)
# Calculate the maximum number of tracks an artist can have in a balanced list
max_tracks_per_artist = (total_tracks + gap) // (gap + 1)
# Check if it's mathematically possible to balance the tracklist
can_be_balanced = all(count <= max_tracks_per_artist for count in artist_counts.values())
can_be_balanced = all(
count <= max_tracks_per_artist for count in artist_counts.values()
)
if not can_be_balanced:
return False, False
@@ -58,7 +72,7 @@ def is_tracklist_balanced(tracks: List[Track], gap: int = 3) -> Tuple[bool, bool
artist_last_positions = {}
for i, track in enumerate(tracks):
artist = track.artists[0]['artisthash']
artist = track.artists[0]["artisthash"]
if artist in artist_last_positions:
if i - artist_last_positions[artist] <= gap:
is_currently_balanced = False
@@ -67,10 +81,14 @@ def is_tracklist_balanced(tracks: List[Track], gap: int = 3) -> Tuple[bool, bool
return can_be_balanced, is_currently_balanced
def balance_mix(tracks: List[Track]) -> List[Track]:
"""
Balances the mix by ensuring that the tracks in a mix are distributed evenly.
Preserves the overall rating order of tracks while minimizing disruption.
Tracks that need to be moved are moved down the tracklist until they no longer
violate the gap rule.
"""
can_be_balanced, is_balanced = is_tracklist_balanced(tracks)