summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-26 18:09:44 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-26 18:09:44 +0200
commit6fb948045b9c563ce591da289cdac1df6bed360c (patch)
tree1a783c4ed5dcf23bc65d6bb8825906473670d3aa /packages/meshbay-hub
parent80cb6c4dc5f22336387f2ac74ef2cb85cf2cf7d4 (diff)
downloadmeshbay-6fb948045b9c563ce591da289cdac1df6bed360c.tar.gz
fix(music): stop sharing one folder's cover across unrelated tracks, and merge various-artists compilations into one album
Two real, confirmed bugs in a large flat music library: - enrich_audio.py's sibling-cover fallback assumed one folder is one release. A large flat "chart ranking" folder mixing dozens of unrelated artists carried several distinct WMP AlbumArt-cache guids (one per original album a track was ripped from), and the fallback picked whichever one WMP had copied to Folder.jpg — attaching one unrelated release's cover to every other track in the folder. Now refuses to pick a cover at all once 2+ distinct guids show up, rather than guess. - music-app.js's groupMusicEntries grouped by artist first, album second, so a various-artists compilation (many genuinely different per-track artists, one shared album tag, no album-artist tag at all — a real ~20-track soundtrack rip has exactly this shape) could never be recognized as one release: every track landed alone in its own artist's bucket and got folded into a singleton pile. Now detects an album key shared across 2+ distinct artist keys and merges those tracks into one compilation card under a "Various" heading instead. Both verified against real, previously-affected files and live in the browser: the shared wrong cover is gone, and the compilation renders as one card with all its tracks in order.
Diffstat (limited to 'packages/meshbay-hub')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/music-app.js45
1 files changed, 44 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/music-app.js b/packages/meshbay-hub/src/meshbay_hub/static/music-app.js
index ae482ad..dd354d9 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/music-app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/music-app.js
@@ -86,6 +86,46 @@ function groupMusicEntries(entries, audioRoot) {
const byTitle = (a, b) => (a.display_title || a.name).localeCompare(b.display_title || b.name);
tracks.sort(byTitle);
+ // A various-artists compilation (a real film/game soundtrack is the
+ // common shape: dozens of genuinely different per-track artists sharing
+ // one correctly-tagged album name, confirmed against a real ~20-track
+ // soundtrack rip with no separate "album artist" tag at all -- this era
+ // of rip never wrote one). Grouping by artist first, as above, can never
+ // recognize this: every track lands alone in its own artist's bucket as
+ // a one-track "album", each one then folded below into that artist's own
+ // singleton pile -- the same release rendered as a wall of disconnected
+ // one-track cards under a dozen different artist headings instead of one.
+ // Detected the only way the data actually supports here (no album-artist
+ // tag survived): the same album key reappears under two or more
+ // genuinely different artist keys. Pulled out and merged *before* the
+ // per-artist singleton folding below, so those tracks never reach it
+ // under their original artist bucket.
+ const albumKeyArtists = new Map(); // albumKey -> Set(artistKey)
+ for (const [artistKey, bucket] of byArtistKey) {
+ for (const albumKey of bucket.albumsByKey.keys()) {
+ if (!albumKeyArtists.has(albumKey)) albumKeyArtists.set(albumKey, new Set());
+ albumKeyArtists.get(albumKey).add(artistKey);
+ }
+ }
+ const compilations = [];
+ for (const [albumKey, artistKeys] of albumKeyArtists) {
+ if (artistKeys.size < 2) continue;
+ const compTracks = [];
+ let albumDisplay = null;
+ for (const artistKey of artistKeys) {
+ const bucket = byArtistKey.get(artistKey);
+ const album = bucket.albumsByKey.get(albumKey);
+ if (albumDisplay == null) albumDisplay = album.album;
+ compTracks.push(...album.tracks);
+ bucket.albumsByKey.delete(albumKey);
+ }
+ compTracks.sort((a, b) => (trackNo(a) - trackNo(b)) || byTitle(a, b));
+ compilations.push({
+ artist: t('music.various'), album: albumDisplay, isUnknown: false, tracks: compTracks,
+ });
+ }
+ compilations.sort((a, b) => a.album.localeCompare(b.album));
+
const artists = [...byArtistKey.values()].map(({ artist, albumsByKey, loose }) => {
const sortedAlbums = [...albumsByKey.values()].sort((a, b) => a.album.localeCompare(b.album));
@@ -123,7 +163,10 @@ function groupMusicEntries(entries, audioRoot) {
});
}
return { artist, albums: realAlbums };
- }).sort((a, b) => a.artist.localeCompare(b.artist));
+ }).filter((a) => a.albums.length > 0);
+
+ if (compilations.length > 0) artists.push({ artist: t('music.various'), albums: compilations });
+ artists.sort((a, b) => a.artist.localeCompare(b.artist));
const albums = artists.flatMap((a) => a.albums);
return { tracks, artists, albums };