From 53ef315f50d55dfffd24ce205a96d4e30d1b4db8 Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Thu, 8 Jan 2026 15:18:23 +0000 Subject: [PATCH] feat(musicbrainz)!: Improve ENV presets * Adds `fields` and `id` presets to give ENV users more flexibility * BREAKING: Removes searchOrder from `sensible` so users must now use `default` with presets (other than `sensible`) to get default searchOrder methods added --- .../configuration/transforms/musicbrainz.mdx | 44 +++++++--- .../transforms/MusicbrainzTransformer.ts | 85 ++++++++++--------- 2 files changed, 74 insertions(+), 55 deletions(-) diff --git a/docsite/docs/configuration/transforms/musicbrainz.mdx b/docsite/docs/configuration/transforms/musicbrainz.mdx index ddd007f3..40783d6e 100644 --- a/docsite/docs/configuration/transforms/musicbrainz.mdx +++ b/docsite/docs/configuration/transforms/musicbrainz.mdx @@ -240,10 +240,12 @@ MB_CONTACT=contact@mydomain.com To configure [stage defaults](#stage-configuration) use `MB_PRESETS` with a comma-delimited list of presets you wish to apply. More than one preset can be applied, in which case they combine. You must choose at least one of the presets below: -* `default` - Applies no defaults. This is the same as using no options from [Matching With Musicbrainz](#matching-with-musicbrainz) -* `sensible` - Applies the [Sensible Default](#sensible-default) configuration for sorting releases -* `native` - Applies `fallbackArtistSearch: native` for [Artist Extraction](#artist-extraction) if the first search fails to find matches -* `aggressive` - Applies [Free Text Search](#free-text) `fallbackFreeTextSearch: true` if all other searches fail +* `default` - Adds the default [`searchOrder`](#search-methods) `isrc`+`basic` (same as using no presets that affect `searchOrder`) +* `id` - Adds [`searchOrder`](#search-methods) methods that priorites searching by ID: `isrc` + `mbidrecording` + `basicOrIds` + `basic` +* `native` - Adds **Extracted Artists** (`artist`) in `native` mode to [`searchOrder`](#search-methods) +* `aggressive` - Adds **Free Text Search** (`freetext`) to [`searchOrder`](#search-methods) +* `fields` - Applies [Field Scoring](#field-scoring) to match sorting so match text is baised towards similarity to original scrobble +* `sensible` - Applies the [Sensible Default](#sensible-default) configuration for [sorting releases](#sorting-releases) Finally, use ENV `*_TRANSFORMS=musicbrainz` on each Source/Client you wish to apply this stage to. This applies the stage in the [`preTransform` Hook](/configuration/transforms#lifecycle-hooks) with all [Rules](#rules) enabled. @@ -266,16 +268,20 @@ services: environment: // highlight-start - MB_CONTACT=contact@mydomain.com - - MB_PRESETS=sensible,native # creates sensible defaults with native fallback searching + # searches with sensible release sorting with the searchOrder + # isrc => basic => artist (native mode) + - MB_PRESETS=default,sensible,native // highlight-end - JELLYFIN_URL=192.168.0.110:8096 - JELLYFIN_APIKEY=c9fae8756fbf481ebd9c5bb56b - JELLYFIN_USER=MyUser // highlight-start - - JELLYFIN_TRANSFORMS=musicbrainz # applies musicbrainz Stage to preTransform of Jellyfin source + # applies musicbrainz Stage to preTransform of Jellyfin source + - JELLYFIN_TRANSFORMS=musicbrainz // highlight-end - - MALOJA_URL=http://192.168.0.100:42010 # maloja receives enhanced scrobble from Jellyfin + # maloja receives enhanced scrobble from Jellyfin + - MALOJA_URL=http://192.168.0.100:42010 - MALOJA_API_KEY=myApiKey @@ -365,7 +371,7 @@ To set search methods and their order use the `searchOrder` option in your [Stag :::tip[Default Search Methods] -If `searchOrder` is undefined, or you use the [Sensible Default/Preset](#sensible-default), then Multi-scrobbler will default to using `isrc` and `basic` methods, in that order. +If `searchOrder` is undefined then Multi-scrobbler will default to using `isrc` then `basic` method. ::: @@ -788,16 +794,26 @@ For a more opinionated match that will mirror what you would expect from data fr } ``` -Consider adding [Artist Extraction](./?fallbackArtist=native#artist-extraction) in **Native Mode** if any of your Sources do not support multiple artists or your music collection is not tagged well. +Consider adding [Artist Extraction (`artist`)](#search-methods) in **Native Mode** if any of your Sources do not support multiple artists or your music collection is not tagged well. + +
```json5 { - // add the line below to the sensible defaults above - // (don't forget commas) - "fallbackArtistSearch": "native" + // use official release over anything else + "releaseStatusPriority": ["official"], + // prefer album, then single, then ep + "releaseGroupPrimaryTypePriority": ["album", "single", "ep"], + // prefer worldwide release + "releaseCountryPriority": ["XW"], + // search using extracted artist as last search attempt + "searchOrder": ["isrc", "basic", "artist"], + "searchArtistMethod": "native" } ``` +
+ ### Search Considerations How complete, and accurate, your scrobble data is from a certain Source should dictate what [Search Methods](#search-methods) you should use for matching. @@ -897,7 +913,7 @@ If Enable logging by turning on [**Debug Mode**](/configuration#debug-mode) to help diagnose any issues with the Musicbrainz API and Scrobble enhancement. **Before creating an issue** please enable logging and include any logs with your issue as this is needed to debug. -If you have multiple Modification Stages and need to see the diff for your Play between each Stage, enable `"log": "all"` in the individual [Modification Stage](http://localhost:3000/docs/configuration/transforms/musicbrainz/?fallbackArtist=native#artist-extraction). +If you have multiple Modification Stages and need to see the diff for your Play between each Stage, enable `"log": "all"` in the individual [Modification Stage](#rules-and-hooks).
@@ -1101,7 +1117,7 @@ In a [Jellyfin](/configuration/clients/jellyfin) [File Config](/configuration?co * Musicbrainz Stage configured with * [Sensible Defaults](#sensible-default) - * Fallback search with [Artist Extraction](#retrying-search) + * Fallback search with [Artist Extraction](#search-methods) * [Default Native Stage](/configuration/transforms/native/#default-stage) * Uses [Flow Control](/configurations/transforms#flow-control) to run Native only if there are no Musicbrainz matches diff --git a/src/backend/common/transforms/MusicbrainzTransformer.ts b/src/backend/common/transforms/MusicbrainzTransformer.ts index 21867161..5a8ce1ae 100644 --- a/src/backend/common/transforms/MusicbrainzTransformer.ts +++ b/src/backend/common/transforms/MusicbrainzTransformer.ts @@ -873,25 +873,46 @@ export const rankReleasesByPriority = (list: IRecordingMatch[], stageConfig: Mus return rankedList; }; -export const DEFAULTS_SENSIBLE: {searchArtistMethod: "native", [key: string]: any} = { +export const DEFAULTS_SENSIBLE: MusicbrainzTransformerData = { // use official release over anything else "releaseStatusPriority": ["official"], // prefer album, then single, then ep "releaseGroupPrimaryTypePriority": ["album", "single", "ep"], // prefer worldwide release - "releaseCountryPriority": ["XW"], - "searchArtistMethod": "native", - "searchOrder": ["isrc","basic","artist"] + "releaseCountryPriority": ["XW"] } -export const DEFAULTS_NATIVE: {searchArtistMethod: "native", "searchOrder": ["artist"]} = { +export const DEFAULTS_NATIVE: MusicbrainzTransformerData = { "searchArtistMethod": "native", "searchOrder": ["artist"] } -export const DEFAULTS_AGGRESSIVE = { +export const DEFAULTS_AGGRESSIVE: MusicbrainzTransformerData = { "searchOrder": ["freetext"] } +export const DEFAULTS_FIELDS_BIAS = { + "titleWeight": 0.33, + "albumWeight": 0.33, + "artistWeight": 0.33 +} + +export const DEFAULTS_PRESET: MusicbrainzTransformerData = { + "searchOrder": ["isrc", "basic"] +} + +export const DEFAULTS_ID: MusicbrainzTransformerData = { + "searchOrder": ["isrc", "mbidrecording", "basicorids", "basic"] +}; + +const PRESETS: Record = { + default: DEFAULTS_PRESET, + sensible: DEFAULTS_SENSIBLE, + native: DEFAULTS_NATIVE, + aggressive: DEFAULTS_AGGRESSIVE, + fields: DEFAULTS_FIELDS_BIAS, + 'id': DEFAULTS_ID +} + export const configFromEnv = (logger: MaybeLogger = new MaybeLogger()) => { const mbEnv = process.env.MB_PRESETS; const mbContact = process.env.MB_CONTACT; @@ -916,43 +937,25 @@ export const configFromEnv = (logger: MaybeLogger = new MaybeLogger()) => { } const presets = mbEnv.split(',').map(x => x.trim().toLocaleLowerCase()); const soSet = new Set(); - for (const p of presets) { - switch (p) { - case 'default': - break; - case 'sensible': - mbConfig.defaults = { - ...mbConfig.defaults, - ...DEFAULTS_SENSIBLE, - searchOrder: undefined, - } - for(const o of DEFAULTS_SENSIBLE.searchOrder) { - soSet.add(o as SearchType); - } - break; - case 'native': - mbConfig.defaults = { - ...mbConfig.defaults, - ...DEFAULTS_NATIVE, - searchOrder: undefined, - } - for(const o of DEFAULTS_NATIVE.searchOrder) { - soSet.add(o as SearchType); - } - break; - case 'aggressive': - mbConfig.defaults = { - ...mbConfig.defaults, - ...DEFAULTS_AGGRESSIVE, - searchOrder: undefined, - } - for(const o of DEFAULTS_AGGRESSIVE.searchOrder) { - soSet.add(o as SearchType); - } - break; + for (const pName of presets) { + const p = PRESETS[pName]; + if(p === undefined) { + logger.warn(`No preset with name '${p}'`); + continue; + } + const { searchOrder = [], ...rest } = p; + mbConfig.defaults = { + ...mbConfig.defaults, + ...rest, } + for (const o of searchOrder) { + soSet.add(o); + } + } + + if(soSet.size > 0) { + mbConfig.defaults.searchOrder = Array.from(soSet); } - mbConfig.defaults.searchOrder = Array.from(soSet); logger.debug(`Using presets: ${presets.join(',')}`); } -- 2.51.2