From 9ec9de519e2dc73fa72b5b7b290443c84d6a5152 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Mon, 19 Jan 2026 21:08:10 +0000 Subject: [PATCH] fix(musicbrainz): find correct release when release or track mbid is present --- .../transforms/MusicbrainzTransformer.ts | 68 ++++++++++++++++++- .../tests/musicbrainz/musicbrainz.test.ts | 63 +++++++++++++++++ 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/src/backend/common/transforms/MusicbrainzTransformer.ts b/src/backend/common/transforms/MusicbrainzTransformer.ts index 8645e321..abdcb016 100644 --- a/src/backend/common/transforms/MusicbrainzTransformer.ts +++ b/src/backend/common/transforms/MusicbrainzTransformer.ts @@ -8,7 +8,7 @@ import { ARTIST_WEIGHT, DELIMITERS, MUSICBRAINZ_URL, MusicbrainzApiConfigData, T import { MaybeLogger } from "../logging.js"; import { childLogger, Logger } from "@foxxmd/logging"; import { MusicbrainzApiClient, MusicbrainzApiConfig, recordingToPlay, UsingTypes } from "../vendor/musicbrainz/MusicbrainzApiClient.js"; -import { IRecordingList, IRecordingMatch, MusicBrainzApi } from "musicbrainz-api"; +import { IRecordingList, IRecordingMatch, IRelease, MusicBrainzApi } from "musicbrainz-api"; import { intersect, isDebugMode, missingMbidTypes, removeUndefinedKeys } from "../../utils.js"; import { SimpleError, SkipTransformStageError, StagePrerequisiteError } from "../errors/MSErrors.js"; import { parseArrayFromMaybeString, scoreNormalizedStringsWeighted } from "../../utils/StringUtils.js"; @@ -603,6 +603,20 @@ export default class MusicbrainzTransformer extends AtomicPartsTransformer x.score >= score); if(filteredList.length === 0) { throw new StagePrerequisiteError(`All ${transformData.count} fetched matches had a score < ${score}, best match was ${transformData.recordings[0].score}`, {shortStack: true}); @@ -829,6 +843,58 @@ export const filterByValidReleaseCountry = (list: IRecordingMatch[], stageConfig || x.releases.length > 0); } +export const filterByExplicitTrackMbid = (list: IRecordingMatch[], play: PlayObject): [IRecordingMatch[], boolean] => { + if (play.data.meta?.brainz?.track === undefined) { + return [list, false]; + } + let recMatch: IRecordingMatch, + releaseMatchId: string; + for (const rec of list) { + if (recMatch !== undefined) { + break; + } + for (const rel of rec.releases) { + // @ts-ignore + if (rel.media.some(x => x.track.some(y => y.id === play.data.meta.brainz.track))) { + releaseMatchId = rel.id; + recMatch = rec; + break; + } + } + } + if (recMatch !== undefined) { + const r = structuredClone(recMatch); + r.releases = r.releases.filter(x => x.id === releaseMatchId); + return [[r], true]; + } + return [list, false]; +} + +export const filterByExplicitReleaseMbid = (list: IRecordingMatch[], play: PlayObject): [IRecordingMatch[], boolean] => { + if (play.data.meta?.brainz?.album === undefined) { + return [list, false]; + } + let recMatch: IRecordingMatch, + releaseMatchId: string; + for (const rec of list) { + if (recMatch !== undefined) { + break; + } + for (const rel of rec.releases) { + if(rel.id === play.data.meta.brainz.album) { + releaseMatchId = rel.id; + recMatch = rec; + } + } + } + if (recMatch !== undefined) { + const r = structuredClone(recMatch); + r.releases = r.releases.filter(x => x.id === releaseMatchId); + return [[r], true]; + } + return [list, false]; +} + export const rankReleasesByPriority = (list: IRecordingMatch[], stageConfig: MusicbrainzTransformerDataStage, play: PlayObject, logger: MaybeLogger = new MaybeLogger()): RecordingRankedMatched[] => { const { releaseStatusPriority = [], diff --git a/src/backend/tests/musicbrainz/musicbrainz.test.ts b/src/backend/tests/musicbrainz/musicbrainz.test.ts index 05c9882d..8f00e0e7 100644 --- a/src/backend/tests/musicbrainz/musicbrainz.test.ts +++ b/src/backend/tests/musicbrainz/musicbrainz.test.ts @@ -162,6 +162,69 @@ describe('Musicbrainz API', function () { expect(res.recordings[0].id).to.eq('026fa041-3917-4c73-9079-ed16e36f20f8') }); + it('uses correct release if track mbid is explict', async function (){ + this.timeout(3500); + + const play: PlayObject = { + data: { + track: "Berghain", + artists: ["ROSALÍA", "Björk", "Yves Tumor"], + albumArtists: ["ROSALÍA"], + album: "LUX", + meta: { + brainz: { + track: '47d5358a-d9eb-48db-babb-56284da8056b' + } + } + }, + meta: {} + } + await mbTransformer.tryInitialize(); + + const stageConfig: MusicbrainzTransformerDataStage = { + type: "musicbrainz", + searchWhenMissing: ["artists", "album", "title"], + searchOrder: ['basicorids'] + }; + + const res = await mbTransformer.getTransformerData(play, stageConfig); + expect(res.recordings).to.exist; + expect(res.recordings).to.not.be.empty; + const postFetch = await mbTransformer.handlePostFetch(play, res, stageConfig); + expect(postFetch.data.meta.brainz.album).to.eq('e5913eac-3d74-47af-a3f2-7aa6618f140a'); + }); + + it('uses correct release if release mbid is explict', async function (){ + this.timeout(3500); + + const play: PlayObject = { + data: { + track: "Berghain", + artists: ["ROSALÍA", "Björk", "Yves Tumor"], + albumArtists: ["ROSALÍA"], + album: "LUX", + meta: { + brainz: { + album: 'e5913eac-3d74-47af-a3f2-7aa6618f140a' + } + } + }, + meta: {} + } + await mbTransformer.tryInitialize(); + + const stageConfig: MusicbrainzTransformerDataStage = { + type: "musicbrainz", + searchWhenMissing: ["artists", "album", "title"], + searchOrder: ['basicorids'] + }; + + const res = await mbTransformer.getTransformerData(play, stageConfig); + expect(res.recordings).to.exist; + expect(res.recordings).to.not.be.empty; + const postFetch = await mbTransformer.handlePostFetch(play, res, stageConfig); + expect(postFetch.data.meta.brainz.album).to.eq('e5913eac-3d74-47af-a3f2-7aa6618f140a'); + }); it('tries second query using only track and album', async function () { -- 2.51.2