Skip to content

Commit 45f45c0

Browse files
authored
Improve compilation album parsing to prevent albums fragmentation (#279)
Fixes a bug where compilation albums are parsed as separate albums instead of creating a single junction album with various artists. Fixes #267
1 parent 2b5e93f commit 45f45c0

3 files changed

Lines changed: 32 additions & 6 deletions

File tree

Managers/Database/DMCategoryQueries.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ extension DatabaseManager {
6868
func getAlbumEntities() -> [AlbumEntity] {
6969
do {
7070
return try dbQueue.read { db in
71+
// Prefer the album's primary artist from the album_artists junction
72+
// (which findOrCreateAlbum populates, including "Various Artists" for compilations)
73+
// before falling back to per-track tag aggregates.
7174
let sql = """
7275
SELECT
7376
albums.id,
@@ -76,7 +79,17 @@ extension DatabaseManager {
7679
albums.artwork_data,
7780
albums.release_year,
7881
COALESCE(SUM(tracks.duration), 0) as totalDuration,
79-
COALESCE(NULLIF(MAX(tracks.album_artist), ''), MAX(tracks.artist)) as artistName,
82+
COALESCE(
83+
(SELECT artists.name
84+
FROM album_artists
85+
JOIN artists ON artists.id = album_artists.artist_id
86+
WHERE album_artists.album_id = albums.id
87+
AND album_artists.role = 'primary'
88+
ORDER BY album_artists.position
89+
LIMIT 1),
90+
NULLIF(MAX(tracks.album_artist), ''),
91+
MAX(tracks.artist)
92+
) as artistName,
8093
albums.created_at
8194
FROM albums
8295
LEFT JOIN tracks ON albums.id = tracks.album_id AND tracks.is_duplicate = 0

Managers/Database/DMCleanup.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,24 @@ extension DatabaseManager {
5757

5858
// 2. Now clean up main tables using GRDB where possible
5959

60-
// Get all artist IDs that are still referenced in track_artists
60+
// Get all artist IDs that are still referenced in track_artists OR album_artists.
61+
// Album-only artists (e.g. the canonical "Various Artists" used to anchor
62+
// compilations) have no track_artists rows but must not be considered orphaned.
6163
let artistsWithTracks = try TrackArtist
6264
.select(TrackArtist.Columns.artistId, as: Int64.self)
6365
.distinct()
6466
.fetchSet(db)
65-
66-
// Delete artists that have NO tracks
67+
68+
let artistsWithAlbums = try AlbumArtist
69+
.select(AlbumArtist.Columns.artistId, as: Int64.self)
70+
.distinct()
71+
.fetchSet(db)
72+
73+
let referencedArtists = artistsWithTracks.union(artistsWithAlbums)
74+
75+
// Delete artists that have NO references in either junction
6776
let allArtistIds = try Artist.select(Artist.Columns.id, as: Int64.self).fetchSet(db)
68-
let artistsToDelete = allArtistIds.subtracting(artistsWithTracks)
77+
let artistsToDelete = allArtistIds.subtracting(referencedArtists)
6978

7079
if !artistsToDelete.isEmpty {
7180
let orphanedArtists = try Artist

Managers/Database/DMNormalization.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,14 @@ extension DatabaseManager {
191191
func processTrackAlbum(_ track: inout FullTrack, in db: Database, cache: ScanLookupCache? = nil) throws {
192192
guard !track.album.isEmpty && track.album != "Unknown Album" else { return }
193193

194-
// Determine the album artist (prefer albumArtist field, fallback to first artist from multi-artist string)
194+
// Determine the album artist (prefer albumArtist field, then compilation flag, then first artist from multi-artist string)
195195
let albumArtistName: String?
196196
if let albumArtist = track.albumArtist, !albumArtist.isEmpty {
197197
albumArtistName = albumArtist
198+
} else if track.compilation {
199+
// Compilation flag set but no album artist tag; group all tracks under a canonical name
200+
// so a Various Artists comp doesn't fragment into one album per track artist.
201+
albumArtistName = "Various Artists"
198202
} else if !track.artist.isEmpty && track.artist != "Unknown Artist" {
199203
// Parse the artist field and use the first artist as the album artist
200204
let artists = ArtistParser.parse(track.artist)

0 commit comments

Comments
 (0)