Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions src/components/bulk-musicbrainz-modal/helpers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { describe, expect, it } from 'vitest'
import type { BulkMusicBrainzSong } from '@/features/musicbrainz/bulk-lookup'
import { initialReleaseSearch, releaseArtist, VARIOUS_ARTISTS } from './helpers'

function song(artist: string | null, albumArtist: string | null = null, album = 'Album'): BulkMusicBrainzSong {
return { artist, albumArtist, album } as BulkMusicBrainzSong
}

describe('releaseArtist', () => {
it('uses the album artist when the files carry one', () => {
expect(releaseArtist([song('Artist A', 'Various Artists'), song('Artist B', 'Various Artists')])).toBe(
'Various Artists'
)
expect(releaseArtist([song('Queen'), song('Queen & David Bowie', 'Queen')])).toBe('Queen')
})

it('uses the track artist for a regular album', () => {
expect(releaseArtist([song('Daft Punk'), song('Daft Punk'), song('Daft Punk')])).toBe('Daft Punk')
})

it('searches for Various Artists when every track has a different artist', () => {
expect(releaseArtist([song('Madonna'), song('Prince'), song('Blondie'), song('Toto')])).toBe(VARIOUS_ARTISTS)
})

it('searches for Various Artists when no artist has more than half of the tracks', () => {
expect(releaseArtist([song('Madonna'), song('Madonna'), song('Prince'), song('Blondie')])).toBe(VARIOUS_ARTISTS)
})

it('does not treat guest appearances as different artists', () => {
expect(
releaseArtist([
song('Kendrick Lamar'),
song('Kendrick Lamar feat. SZA'),
song('Kendrick Lamar & Rihanna'),
song('Kendrick Lamar ft. Drake'),
song('Kendrick Lamar')
])
).toBe('Kendrick Lamar')
})

it('keeps a single song and empty selections as they are', () => {
expect(releaseArtist([song('Prince')])).toBe('Prince')
expect(releaseArtist([song(null), song(' ')])).toBe('')
})
})

describe('initialReleaseSearch', () => {
it('fills album and artist for a compilation', () => {
expect(initialReleaseSearch([song('Madonna', null, 'Bravo Hits 1'), song('Prince', null, 'Bravo Hits 1')])).toEqual(
{
album: 'Bravo Hits 1',
artist: VARIOUS_ARTISTS
}
)
})
})
29 changes: 28 additions & 1 deletion src/components/bulk-musicbrainz-modal/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,37 @@ export function mostCommon(values: Array<string | null | undefined>): string {
return [...counts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? ''
}

/** MusicBrainz' own name for releases by several artists (its special purpose artist). */
export const VARIOUS_ARTISTS = 'Various Artists'

/** "Kendrick Lamar feat. SZA" -> "Kendrick Lamar", so guest appearances don't look like different artists. */
function primaryArtist(artist: string): string {
return artist
.split(/\s+(?:feat\.?|ft\.?|featuring|with|&|x|vs\.?)\s+|\s*[,;/]\s*/i)[0]
.trim()
.toLowerCase()
}

/**
* The artist to search the release by: the album artist when the files carry one, otherwise the track artist -
* unless no artist (guests aside) appears on more than half of the songs, which makes it a compilation.
*/
export function releaseArtist(songs: BulkMusicBrainzSong[]): string {
const albumArtist = mostCommon(songs.map(song => song.albumArtist))
if (albumArtist) return albumArtist

const artists = songs.map(song => song.artist?.trim()).filter((artist): artist is string => !!artist)
const primaries = artists.map(primaryArtist)
const top = mostCommon(primaries)
const topCount = primaries.filter(primary => primary === top).length
if (artists.length > 1 && topCount * 2 <= artists.length) return VARIOUS_ARTISTS
return mostCommon(artists.filter(artist => primaryArtist(artist) === top))
}

export function initialReleaseSearch(songs: BulkMusicBrainzSong[]) {
return {
album: mostCommon(songs.map(song => song.album)),
artist: mostCommon(songs.map(song => song.albumArtist || song.artist))
artist: releaseArtist(songs)
}
}

Expand Down
Loading