aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/music-app.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/music-app.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/music-app.js56
1 files changed, 41 insertions, 15 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 87e1319..7e16b53 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/music-app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/music-app.js
@@ -36,13 +36,15 @@ function saveViewMode(mode) {
// -- grouping -------------------------------------------------------------
-// Two tags differing only in case ("Racing With The Sun" vs "Racing with
-// the sun" -- found live, a single mistagged track split one real album
-// into two cards) are the same artist/album for grouping purposes. Folded
-// for the *key* only; the first-seen spelling is kept as the display
-// string, so this never rewrites anyone's tags.
+// Two tags differing only in case (found live: a single mistagged track
+// split one real album into two cards) are the same artist/album for
+// grouping purposes. Same for an "&" vs "and" spelling of the same act,
+// tagged both ways across different rips of the same catalogue. Folded for
+// the *key* only; the first-seen spelling is kept as the display string, so
+// this never rewrites anyone's tags.
function foldKey(s) {
- return (s || '').trim().replace(/\s+/g, ' ').toLowerCase();
+ return (s || '').trim().replace(/\s+/g, ' ').toLowerCase()
+ .replace(/\s*&\s*/g, ' and ').replace(/\s+/g, ' ').trim();
}
function groupMusicEntries(entries) {
@@ -74,18 +76,42 @@ function groupMusicEntries(entries) {
tracks.sort(byTitle);
const artists = [...byArtistKey.values()].map(({ artist, albumsByKey, loose }) => {
- const albums = [...albumsByKey.values()].sort((a, b) => a.album.localeCompare(b.album));
- for (const album of albums) {
+ const sortedAlbums = [...albumsByKey.values()].sort((a, b) => a.album.localeCompare(b.album));
+
+ // A "singleton" -- an album bucket down to exactly one track, because
+ // that's a real album tag but this person only has one song from it,
+ // not the whole release -- clutters the grid exactly the way an
+ // untagged loose track does. Found live: one artist's folder listing a
+ // dozen near-empty one-track album cards alongside the genuine
+ // multi-track albums. Both kinds fold into a single "<artist> -
+ // Various" tile, unless there is only one leftover track overall, where
+ // relabeling away a real album name (or minting "Various" for one
+ // file) buys nothing.
+ const realAlbums = [];
+ const misc = [...loose];
+ const miscSourceAlbums = [];
+ for (const album of sortedAlbums) {
+ if (album.tracks.length > 1) { realAlbums.push(album); continue; }
+ misc.push(...album.tracks);
+ miscSourceAlbums.push(album);
+ }
+ for (const album of realAlbums) {
album.tracks.sort((a, b) => (trackNo(a) - trackNo(b)) || byTitle(a, b));
}
- if (loose.length) {
- // Appended after every real album, never sorted in among them by
- // whatever the placeholder text happens to alphabetize to -- it
- // isn't a release, and translating "Unknown album" must not move it.
- loose.sort((a, b) => (trackNo(a) - trackNo(b)) || byTitle(a, b));
- albums.push({ artist, album: t('music.unknown_album'), isUnknown: true, tracks: loose });
+
+ if (misc.length === 1) {
+ // Keep the one leftover's own real album name if it had one; the
+ // generic placeholder only for a single untagged track with nothing
+ // else to call it.
+ realAlbums.push(miscSourceAlbums[0]
+ || { artist, album: t('music.unknown_album'), isUnknown: true, tracks: misc });
+ } else if (misc.length > 1) {
+ misc.sort((a, b) => (trackNo(a) - trackNo(b)) || byTitle(a, b));
+ realAlbums.push({
+ artist, album: `${artist} - ${t('music.various')}`, isUnknown: true, tracks: misc,
+ });
}
- return { artist, albums };
+ return { artist, albums: realAlbums };
}).sort((a, b) => a.artist.localeCompare(b.artist));
const albums = artists.flatMap((a) => a.albums);