From c4ecfeedeccdb4410582fbbf771509bd659e368e Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sat, 7 Feb 2026 13:36:17 -0800 Subject: [PATCH] add automatic country prioritization based on existing releases --- src/i18n/locales/en.json | 10 ++++--- src/renderer/api/query-keys.ts | 2 ++ .../album-artist-detail-content.tsx | 5 ++++ .../musicbrainz/api/musicbrainz-api.ts | 20 +++++++++++++- .../integrations/integrations-tab.tsx | 26 ++++++++++++++++--- src/renderer/store/settings.store.ts | 14 +++++++++- 6 files changed, 67 insertions(+), 10 deletions(-) diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index ef79faeeb..7fef65280 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -897,12 +897,14 @@ "musicbrainz": "show MusicBrainz links", "musicBrainzQueries": "enable MusicBrainz integration", "musicBrainzQueries_description": "the integration will query MusicBrainz for missing artist releases and other miscellaneous data", - "musicbrainzExcludeReleaseTypes": "exclude MusicBrainz release types", + "musicbrainzExcludeReleaseTypes": "MusicBrainz release type exclusion", "musicbrainzExcludeReleaseTypes_description": "release types to exclude when loading MusicBrainz artist releases", - "musicbrainzPrioritizeCountries": "prioritize MusicBrainz countries", + "musicbrainzPrioritizeCountries": "MusicBrainz country priority", "musicbrainzPrioritizeCountries_description": "countries to prioritize when ordering MusicBrainz releases (first in list has highest priority)", - "youtube": "enable youtube integration", - "youtube_description": "external songs will attempt to use YouTube to resolve stream URLs for playback (desktop only)", + "musicbrainzAutoCountryPriority": "automatic country priority", + "musicbrainzAutoCountryPriority_description": "derive country priority from the artist's MusicBrainz releases (countries with more releases are ranked higher)", + "youtube": "enable YouTube playback", + "youtube_description": "external songs will attempt to use YouTube to resolve stream URLs (desktop only)", "neteaseTranslation_description": "When enabled, fetches and displays translated lyrics from NetEase if available", "neteaseTranslation": "Enable NetEase translations", "notify": "enable song notifications", diff --git a/src/renderer/api/query-keys.ts b/src/renderer/api/query-keys.ts index d30dfb2be..479b32e5d 100644 --- a/src/renderer/api/query-keys.ts +++ b/src/renderer/api/query-keys.ts @@ -275,6 +275,7 @@ export const queryKeys: Record< limit: number | undefined, mbzArtistId: string, config?: { + autoCountryPriority: boolean; excludeReleaseTypes: string[]; prioritizeCountries: string[]; }, @@ -286,6 +287,7 @@ export const queryKeys: Record< limit, config ? [ + String(config.autoCountryPriority), [...config.excludeReleaseTypes].sort().join(','), [...config.prioritizeCountries].sort().join(','), ] diff --git a/src/renderer/features/artists/components/album-artist-detail-content.tsx b/src/renderer/features/artists/components/album-artist-detail-content.tsx index e037a2c01..d40d0149f 100644 --- a/src/renderer/features/artists/components/album-artist-detail-content.tsx +++ b/src/renderer/features/artists/components/album-artist-detail-content.tsx @@ -1381,6 +1381,9 @@ const ArtistAlbums = ({ albumsQuery }: ArtistAlbumsProps) => { const musicBrainzExcludeReleaseTypes = useSettingsStore( (state) => state.integrations.musicBrainzExcludeReleaseTypes, ); + const musicbrainzAutoCountryPriority = useSettingsStore( + (state) => state.integrations.musicbrainzAutoCountryPriority, + ); const musicBrainzPrioritizeCountries = useSettingsStore( (state) => state.integrations.musicBrainzPrioritizeCountries, ); @@ -1410,6 +1413,7 @@ const ArtistAlbums = ({ albumsQuery }: ArtistAlbumsProps) => { const musicBrainzEnabled = useSettingsStore((state) => state.integrations.musicBrainz); const musicbrainzArtistQuery = useQuery({ ...musicbrainzQueries.artist({ + autoCountryPriority: musicbrainzAutoCountryPriority, excludeReleaseTypes: musicBrainzExcludeReleaseTypes, mbzArtistId: detailQuery.data?.mbz as string, prioritizeCountries: musicBrainzPrioritizeCountries, @@ -1418,6 +1422,7 @@ const ArtistAlbums = ({ albumsQuery }: ArtistAlbumsProps) => { meta: { albumArtist: detailQuery.data, albums: albumsQuery.data?.items || [], + autoCountryPriority: musicbrainzAutoCountryPriority, excludeReleaseTypes: musicBrainzExcludeReleaseTypes, prioritizeCountries: musicBrainzPrioritizeCountries, }, diff --git a/src/renderer/features/musicbrainz/api/musicbrainz-api.ts b/src/renderer/features/musicbrainz/api/musicbrainz-api.ts index 06ab18958..cd689b3ad 100644 --- a/src/renderer/features/musicbrainz/api/musicbrainz-api.ts +++ b/src/renderer/features/musicbrainz/api/musicbrainz-api.ts @@ -40,6 +40,7 @@ export type IRelationWithWork = IRelation & { work?: IWork }; export type MusicBrainzArtistSelectMeta = { albumArtist: AlbumArtist; albums?: Album[]; + autoCountryPriority?: boolean; excludeReleaseTypes?: string[]; prioritizeCountries?: string[]; }; @@ -123,7 +124,22 @@ const artistSelect = memoize( const excludeReleaseTypes = (meta.excludeReleaseTypes ?? []).map((t) => t.toLowerCase()); const excludeSet = new Set(excludeReleaseTypes); - const prioritizeCountries = (meta.prioritizeCountries ?? []).map((c) => c.toUpperCase()); + + let prioritizeCountries: string[]; + if (meta.autoCountryPriority) { + const countryCounts = new Map(); + for (const release of data.releases.releases) { + const country = release.country?.toUpperCase(); + if (country) { + countryCounts.set(country, (countryCounts.get(country) ?? 0) + 1); + } + } + prioritizeCountries = [...countryCounts.entries()] + .sort(([, a], [, b]) => b - a) + .map(([code]) => code); + } else { + prioritizeCountries = (meta.prioritizeCountries ?? []).map((c) => c.toUpperCase()); + } const releaseEntries = Array.from(unownedReleases.entries()) .filter(([, release]) => { @@ -290,11 +306,13 @@ const EMPTY_BROWSE_RELEASES: IBrowseReleasesResult = { export const musicbrainzQueries = { artist: (args: { + autoCountryPriority?: boolean; excludeReleaseTypes?: string[]; mbzArtistId: string; prioritizeCountries?: string[]; }) => { const config = { + autoCountryPriority: args.autoCountryPriority ?? false, excludeReleaseTypes: args.excludeReleaseTypes ?? [], prioritizeCountries: args.prioritizeCountries ?? [], }; diff --git a/src/renderer/features/settings/components/integrations/integrations-tab.tsx b/src/renderer/features/settings/components/integrations/integrations-tab.tsx index 925c0112b..bd164d156 100644 --- a/src/renderer/features/settings/components/integrations/integrations-tab.tsx +++ b/src/renderer/features/settings/components/integrations/integrations-tab.tsx @@ -378,6 +378,27 @@ export const IntegrationsTab = memo(() => { isHidden: !musicBrainz || !settings.musicBrainz, title: t('setting.musicbrainzPrioritizeCountries', { postProcess: 'sentenceCase' }), }, + { + control: ( + + updateIntegrations({ + musicbrainzAutoCountryPriority: e.currentTarget.checked, + }) + } + /> + ), + description: t('setting.musicbrainzAutoCountryPriority', { + context: 'description', + postProcess: 'sentenceCase', + }), + isHidden: !musicBrainz || !settings.musicBrainz, + title: t('setting.musicbrainzAutoCountryPriority', { postProcess: 'sentenceCase' }), + }, { control: ( { return ( - + ); }); diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts index 246984728..858e035d9 100644 --- a/src/renderer/store/settings.store.ts +++ b/src/renderer/store/settings.store.ts @@ -614,6 +614,7 @@ const QueryBuilderSettingsSchema = z.object({ }); const IntegrationsSettingsSchema = z.object({ + musicbrainzAutoCountryPriority: z.boolean(), musicBrainz: z.boolean(), musicBrainzExcludeReleaseTypes: z.array(z.string()), musicBrainzPrioritizeCountries: z.array(z.string()), @@ -1105,6 +1106,7 @@ const initialState: SettingsState = { globalMediaHotkeys: true, }, integrations: { + musicbrainzAutoCountryPriority: false, musicBrainz: true, musicBrainzExcludeReleaseTypes: [], musicBrainzPrioritizeCountries: [], @@ -2108,6 +2110,7 @@ export const useSettingsStore = createWithEqualityFn()( // Move MusicBrainz release/country options to Integrations; keep musicBrainz in general const general = state.general as Record; state.integrations = { + musicbrainzAutoCountryPriority: initialState.integrations.musicbrainzAutoCountryPriority, musicBrainz: initialState.integrations.musicBrainz, musicBrainzExcludeReleaseTypes: (general?.musicBrainzExcludeReleaseTypes as string[]) ?? @@ -2130,10 +2133,19 @@ export const useSettingsStore = createWithEqualityFn()( }; } + if (version <= 26) { + state.integrations = { + ...state.integrations, + musicbrainzAutoCountryPriority: + (state.integrations as { musicbrainzAutoCountryPriority?: boolean }) + ?.musicbrainzAutoCountryPriority ?? false, + }; + } + return persistedState; }, name: 'store_settings', - version: 26, + version: 27, }, ), );