Add metadata source filter to library and fix Discogs enrichment

Library page: new dropdown filter to show artists matched or unmatched
to any metadata source (Spotify, MusicBrainz, Deezer, Discogs, etc).
Select "No Discogs" to find artists needing manual Discogs matching.
Filter applied as WHERE clause on the source ID columns.

Discogs enrichment: added to valid_services whitelist, _enrichment_locks,
and _run_single_enrichment handler. The Enrich button was returning an
error when Discogs was selected from the dropdown.
pull/289/head
Broque Thomas 1 month ago
parent d597123a40
commit e65b6bab67

@ -7905,7 +7905,7 @@ class MusicDatabase:
'server_source': server_source
}
def get_library_artists(self, search_query: str = "", letter: str = "", page: int = 1, limit: int = 50, watchlist_filter: str = "all", profile_id: int = 1) -> Dict[str, Any]:
def get_library_artists(self, search_query: str = "", letter: str = "", page: int = 1, limit: int = 50, watchlist_filter: str = "all", profile_id: int = 1, source_filter: str = "") -> Dict[str, Any]:
"""
Get artists for the library page with search, filtering, and pagination
@ -7915,6 +7915,7 @@ class MusicDatabase:
page: Page number (1-based)
limit: Number of results per page
watchlist_filter: Filter by watchlist status ("all", "watched", "unwatched")
source_filter: Filter by metadata source match (e.g. "spotify", "!spotify" for unmatched)
Returns:
Dict containing artists list, pagination info, and total count
@ -7940,6 +7941,29 @@ class MusicDatabase:
where_conditions.append("UPPER(SUBSTR(name, 1, 1)) = UPPER(?)")
params.append(letter)
# Metadata source filter — match or exclude by enrichment source
if source_filter:
_source_columns = {
'spotify': 'a.spotify_artist_id',
'musicbrainz': 'a.musicbrainz_id',
'deezer': 'a.deezer_id',
'discogs': 'a.discogs_id',
'audiodb': 'a.audiodb_id',
'itunes': 'a.itunes_artist_id',
'lastfm': 'a.lastfm_url',
'genius': 'a.genius_url',
'tidal': 'a.tidal_id',
'qobuz': 'a.qobuz_id',
}
negate = source_filter.startswith('!')
key = source_filter.lstrip('!')
col = _source_columns.get(key)
if col:
if negate:
where_conditions.append(f"({col} IS NULL OR {col} = '')")
else:
where_conditions.append(f"({col} IS NOT NULL AND {col} != '')")
# Get active server for filtering
from config.settings import config_manager
active_server = config_manager.get_active_media_server()

@ -9365,6 +9365,7 @@ def get_library_artists():
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 75))
watchlist_filter = request.args.get('watchlist', 'all')
source_filter = request.args.get('source_filter', '')
# Get database instance
database = get_database()
@ -9376,7 +9377,8 @@ def get_library_artists():
page=page,
limit=limit,
watchlist_filter=watchlist_filter,
profile_id=get_current_profile_id()
profile_id=get_current_profile_id(),
source_filter=source_filter
)
# Fix image URLs for all artists

@ -2633,6 +2633,37 @@
</button>
</div>
<!-- Metadata Source Filter -->
<div class="library-source-filter">
<select id="library-source-filter" class="library-source-filter-select">
<option value="">All Sources</option>
<optgroup label="Unmatched to">
<option value="!spotify">No Spotify</option>
<option value="!musicbrainz">No MusicBrainz</option>
<option value="!deezer">No Deezer</option>
<option value="!discogs">No Discogs</option>
<option value="!audiodb">No AudioDB</option>
<option value="!itunes">No iTunes</option>
<option value="!lastfm">No Last.fm</option>
<option value="!genius">No Genius</option>
<option value="!tidal">No Tidal</option>
<option value="!qobuz">No Qobuz</option>
</optgroup>
<optgroup label="Matched to">
<option value="spotify">Has Spotify</option>
<option value="musicbrainz">Has MusicBrainz</option>
<option value="deezer">Has Deezer</option>
<option value="discogs">Has Discogs</option>
<option value="audiodb">Has AudioDB</option>
<option value="itunes">Has iTunes</option>
<option value="lastfm">Has Last.fm</option>
<option value="genius">Has Genius</option>
<option value="tidal">Has Tidal</option>
<option value="qobuz">Has Qobuz</option>
</optgroup>
</select>
</div>
<!-- Alphabet Selector -->
<div class="alphabet-selector" id="alphabet-selector">
<div class="alphabet-selector-inner">

@ -41208,7 +41208,8 @@ const libraryPageState = {
currentPage: 1,
limit: 75,
debounceTimer: null,
watchlistFilter: "all"
watchlistFilter: "all",
sourceFilter: ""
};
function initializeLibraryPage() {
@ -41221,6 +41222,9 @@ function initializeLibraryPage() {
// Initialize watchlist filter
initializeWatchlistFilter();
// Initialize metadata source filter
initializeSourceFilter();
// Initialize alphabet selector
initializeAlphabetSelector();
@ -41302,6 +41306,16 @@ function initializeWatchlistFilter() {
});
}
function initializeSourceFilter() {
const select = document.getElementById('library-source-filter');
if (!select) return;
select.addEventListener('change', () => {
libraryPageState.sourceFilter = select.value;
libraryPageState.currentPage = 1;
loadLibraryArtists();
});
}
function initializeAlphabetSelector() {
const alphabetButtons = document.querySelectorAll(".alphabet-btn");
@ -41355,6 +41369,7 @@ async function loadLibraryArtists() {
limit: libraryPageState.limit,
watchlist: libraryPageState.watchlistFilter
});
if (libraryPageState.sourceFilter) params.set('source_filter', libraryPageState.sourceFilter);
// Fetch artists from API
const response = await fetch(`/api/library/artists?${params}`);

@ -21798,6 +21798,44 @@ body.helper-mode-active #dashboard-activity-feed:hover {
padding: 8px 0;
}
.library-source-filter {
display: flex;
justify-content: center;
padding: 0 0 4px;
}
.library-source-filter-select {
padding: 6px 30px 6px 12px;
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.05);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='rgba(255,255,255,0.35)' d='M2 4l4 4 4-4'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 10px center;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
color: rgba(255, 255, 255, 0.6);
font-size: 12px;
font-weight: 500;
font-family: inherit;
border-radius: 8px;
cursor: pointer;
transition: border-color 0.2s, color 0.2s;
min-width: 140px;
}
.library-source-filter-select:hover {
border-color: rgba(255, 255, 255, 0.2);
color: rgba(255, 255, 255, 0.8);
}
.library-source-filter-select:focus {
border-color: rgba(var(--accent-rgb), 0.4);
outline: none;
}
.library-source-filter-select option,
.library-source-filter-select optgroup {
background: #1a1a1e;
color: #fff;
}
.watchlist-filter-btn {
padding: 6px 16px;
border: 1px solid rgba(255, 255, 255, 0.1);

Loading…
Cancel
Save