Files
swingmusic-extended/app/utils/mixes.py
T
2024-10-27 06:35:37 +01:00

96 lines
3.6 KiB
Python

from app.models.track import Track
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:
"""
Check if placing the track at the given position violates the gap rule.
"""
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)
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:
"""
Find the next available position for the track, starting from 'start' and wrapping around.
"""
for i in range(start, total_tracks):
if i not in balanced_mix and not violates_gap_rule(balanced_mix, i, track):
return i
for i in range(start):
if i not in balanced_mix and not violates_gap_rule(balanced_mix, i, 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.
Args:
- tracks: List of Track objects
- gap: Minimum number of tracks between songs by the same artist (default 3)
Returns:
- A tuple (can_be_balanced, is_currently_balanced)
"""
total_tracks = len(tracks)
# Count tracks per artist (considering only the first artist)
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())
if not can_be_balanced:
return False, False
# Check if the current arrangement is balanced
is_currently_balanced = True
artist_last_positions = {}
for i, track in enumerate(tracks):
artist = track.artists[0]['artisthash']
if artist in artist_last_positions:
if i - artist_last_positions[artist] <= gap:
is_currently_balanced = False
break
artist_last_positions[artist] = i
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.
"""
can_be_balanced, is_balanced = is_tracklist_balanced(tracks)
if not can_be_balanced:
print("Warning: This tracklist cannot be perfectly balanced.")
# Proceed with best-effort balancing
if is_balanced:
return tracks # Already balanced, no need to modify
balanced_mix: Dict[int, Track] = {}
total_tracks = len(tracks)
for i, track in enumerate(tracks):
if i in balanced_mix or not violates_gap_rule(balanced_mix, i, track):
balanced_mix[i] = track
else:
new_position = find_next_position(balanced_mix, i, track, total_tracks)
balanced_mix[new_position] = track
# Convert the dictionary back to a list, preserving the new order
return [balanced_mix[i] for i in sorted(balanced_mix.keys())]