add automatic country prioritization based on existing releases

This commit is contained in:
jeffvli
2026-02-07 13:36:17 -08:00
parent f43655ed5a
commit c4ecfeedec
6 changed files with 67 additions and 10 deletions
+6 -4
View File
@@ -897,12 +897,14 @@
"musicbrainz": "show MusicBrainz links", "musicbrainz": "show MusicBrainz links",
"musicBrainzQueries": "enable MusicBrainz integration", "musicBrainzQueries": "enable MusicBrainz integration",
"musicBrainzQueries_description": "the integration will query MusicBrainz for missing artist releases and other miscellaneous data", "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", "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)", "musicbrainzPrioritizeCountries_description": "countries to prioritize when ordering MusicBrainz releases (first in list has highest priority)",
"youtube": "enable youtube integration", "musicbrainzAutoCountryPriority": "automatic country priority",
"youtube_description": "external songs will attempt to use YouTube to resolve stream URLs for playback (desktop only)", "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_description": "When enabled, fetches and displays translated lyrics from NetEase if available",
"neteaseTranslation": "Enable NetEase translations", "neteaseTranslation": "Enable NetEase translations",
"notify": "enable song notifications", "notify": "enable song notifications",
+2
View File
@@ -275,6 +275,7 @@ export const queryKeys: Record<
limit: number | undefined, limit: number | undefined,
mbzArtistId: string, mbzArtistId: string,
config?: { config?: {
autoCountryPriority: boolean;
excludeReleaseTypes: string[]; excludeReleaseTypes: string[];
prioritizeCountries: string[]; prioritizeCountries: string[];
}, },
@@ -286,6 +287,7 @@ export const queryKeys: Record<
limit, limit,
config config
? [ ? [
String(config.autoCountryPriority),
[...config.excludeReleaseTypes].sort().join(','), [...config.excludeReleaseTypes].sort().join(','),
[...config.prioritizeCountries].sort().join(','), [...config.prioritizeCountries].sort().join(','),
] ]
@@ -1381,6 +1381,9 @@ const ArtistAlbums = ({ albumsQuery }: ArtistAlbumsProps) => {
const musicBrainzExcludeReleaseTypes = useSettingsStore( const musicBrainzExcludeReleaseTypes = useSettingsStore(
(state) => state.integrations.musicBrainzExcludeReleaseTypes, (state) => state.integrations.musicBrainzExcludeReleaseTypes,
); );
const musicbrainzAutoCountryPriority = useSettingsStore(
(state) => state.integrations.musicbrainzAutoCountryPriority,
);
const musicBrainzPrioritizeCountries = useSettingsStore( const musicBrainzPrioritizeCountries = useSettingsStore(
(state) => state.integrations.musicBrainzPrioritizeCountries, (state) => state.integrations.musicBrainzPrioritizeCountries,
); );
@@ -1410,6 +1413,7 @@ const ArtistAlbums = ({ albumsQuery }: ArtistAlbumsProps) => {
const musicBrainzEnabled = useSettingsStore((state) => state.integrations.musicBrainz); const musicBrainzEnabled = useSettingsStore((state) => state.integrations.musicBrainz);
const musicbrainzArtistQuery = useQuery({ const musicbrainzArtistQuery = useQuery({
...musicbrainzQueries.artist({ ...musicbrainzQueries.artist({
autoCountryPriority: musicbrainzAutoCountryPriority,
excludeReleaseTypes: musicBrainzExcludeReleaseTypes, excludeReleaseTypes: musicBrainzExcludeReleaseTypes,
mbzArtistId: detailQuery.data?.mbz as string, mbzArtistId: detailQuery.data?.mbz as string,
prioritizeCountries: musicBrainzPrioritizeCountries, prioritizeCountries: musicBrainzPrioritizeCountries,
@@ -1418,6 +1422,7 @@ const ArtistAlbums = ({ albumsQuery }: ArtistAlbumsProps) => {
meta: { meta: {
albumArtist: detailQuery.data, albumArtist: detailQuery.data,
albums: albumsQuery.data?.items || [], albums: albumsQuery.data?.items || [],
autoCountryPriority: musicbrainzAutoCountryPriority,
excludeReleaseTypes: musicBrainzExcludeReleaseTypes, excludeReleaseTypes: musicBrainzExcludeReleaseTypes,
prioritizeCountries: musicBrainzPrioritizeCountries, prioritizeCountries: musicBrainzPrioritizeCountries,
}, },
@@ -40,6 +40,7 @@ export type IRelationWithWork = IRelation & { work?: IWork };
export type MusicBrainzArtistSelectMeta = { export type MusicBrainzArtistSelectMeta = {
albumArtist: AlbumArtist; albumArtist: AlbumArtist;
albums?: Album[]; albums?: Album[];
autoCountryPriority?: boolean;
excludeReleaseTypes?: string[]; excludeReleaseTypes?: string[];
prioritizeCountries?: string[]; prioritizeCountries?: string[];
}; };
@@ -123,7 +124,22 @@ const artistSelect = memoize(
const excludeReleaseTypes = (meta.excludeReleaseTypes ?? []).map((t) => t.toLowerCase()); const excludeReleaseTypes = (meta.excludeReleaseTypes ?? []).map((t) => t.toLowerCase());
const excludeSet = new Set(excludeReleaseTypes); const excludeSet = new Set(excludeReleaseTypes);
const prioritizeCountries = (meta.prioritizeCountries ?? []).map((c) => c.toUpperCase());
let prioritizeCountries: string[];
if (meta.autoCountryPriority) {
const countryCounts = new Map<string, number>();
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()) const releaseEntries = Array.from(unownedReleases.entries())
.filter(([, release]) => { .filter(([, release]) => {
@@ -290,11 +306,13 @@ const EMPTY_BROWSE_RELEASES: IBrowseReleasesResult = {
export const musicbrainzQueries = { export const musicbrainzQueries = {
artist: (args: { artist: (args: {
autoCountryPriority?: boolean;
excludeReleaseTypes?: string[]; excludeReleaseTypes?: string[];
mbzArtistId: string; mbzArtistId: string;
prioritizeCountries?: string[]; prioritizeCountries?: string[];
}) => { }) => {
const config = { const config = {
autoCountryPriority: args.autoCountryPriority ?? false,
excludeReleaseTypes: args.excludeReleaseTypes ?? [], excludeReleaseTypes: args.excludeReleaseTypes ?? [],
prioritizeCountries: args.prioritizeCountries ?? [], prioritizeCountries: args.prioritizeCountries ?? [],
}; };
@@ -378,6 +378,27 @@ export const IntegrationsTab = memo(() => {
isHidden: !musicBrainz || !settings.musicBrainz, isHidden: !musicBrainz || !settings.musicBrainz,
title: t('setting.musicbrainzPrioritizeCountries', { postProcess: 'sentenceCase' }), title: t('setting.musicbrainzPrioritizeCountries', { postProcess: 'sentenceCase' }),
}, },
{
control: (
<Switch
aria-label={t('setting.musicbrainzAutoCountryPriority', {
postProcess: 'sentenceCase',
})}
defaultChecked={settings.musicbrainzAutoCountryPriority}
onChange={(e) =>
updateIntegrations({
musicbrainzAutoCountryPriority: e.currentTarget.checked,
})
}
/>
),
description: t('setting.musicbrainzAutoCountryPriority', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: !musicBrainz || !settings.musicBrainz,
title: t('setting.musicbrainzAutoCountryPriority', { postProcess: 'sentenceCase' }),
},
{ {
control: ( control: (
<Switch <Switch
@@ -397,10 +418,7 @@ export const IntegrationsTab = memo(() => {
return ( return (
<Stack gap="md"> <Stack gap="md">
<SettingsSection <SettingsSection options={options} title={'MusicBrainz'} />
options={options}
title={t('page.setting.integrationsTab', { postProcess: 'sentenceCase' })}
/>
</Stack> </Stack>
); );
}); });
+13 -1
View File
@@ -614,6 +614,7 @@ const QueryBuilderSettingsSchema = z.object({
}); });
const IntegrationsSettingsSchema = z.object({ const IntegrationsSettingsSchema = z.object({
musicbrainzAutoCountryPriority: z.boolean(),
musicBrainz: z.boolean(), musicBrainz: z.boolean(),
musicBrainzExcludeReleaseTypes: z.array(z.string()), musicBrainzExcludeReleaseTypes: z.array(z.string()),
musicBrainzPrioritizeCountries: z.array(z.string()), musicBrainzPrioritizeCountries: z.array(z.string()),
@@ -1105,6 +1106,7 @@ const initialState: SettingsState = {
globalMediaHotkeys: true, globalMediaHotkeys: true,
}, },
integrations: { integrations: {
musicbrainzAutoCountryPriority: false,
musicBrainz: true, musicBrainz: true,
musicBrainzExcludeReleaseTypes: [], musicBrainzExcludeReleaseTypes: [],
musicBrainzPrioritizeCountries: [], musicBrainzPrioritizeCountries: [],
@@ -2108,6 +2110,7 @@ export const useSettingsStore = createWithEqualityFn<SettingsSlice>()(
// Move MusicBrainz release/country options to Integrations; keep musicBrainz in general // Move MusicBrainz release/country options to Integrations; keep musicBrainz in general
const general = state.general as Record<string, unknown>; const general = state.general as Record<string, unknown>;
state.integrations = { state.integrations = {
musicbrainzAutoCountryPriority: initialState.integrations.musicbrainzAutoCountryPriority,
musicBrainz: initialState.integrations.musicBrainz, musicBrainz: initialState.integrations.musicBrainz,
musicBrainzExcludeReleaseTypes: musicBrainzExcludeReleaseTypes:
(general?.musicBrainzExcludeReleaseTypes as string[]) ?? (general?.musicBrainzExcludeReleaseTypes as string[]) ??
@@ -2130,10 +2133,19 @@ export const useSettingsStore = createWithEqualityFn<SettingsSlice>()(
}; };
} }
if (version <= 26) {
state.integrations = {
...state.integrations,
musicbrainzAutoCountryPriority:
(state.integrations as { musicbrainzAutoCountryPriority?: boolean })
?.musicbrainzAutoCountryPriority ?? false,
};
}
return persistedState; return persistedState;
}, },
name: 'store_settings', name: 'store_settings',
version: 26, version: 27,
}, },
), ),
); );