feat: autotag scoring overhaul, dump-based explore index, and lyrics search
Consolidates in-progress work across autotag, explore, and library: - autotag: beets/Picard-informed scoring engine — ID-first matching, VA handling, recommendation tiers, and a merged distance/rank cascade, with an eval harness for regression tracking. - explore: offline MusicBrainz dump import/incremental refresh replaces the legacy tier crawl; index-first local search with fuzzy matching and a dedicated ranker; disk-free guards for dump downloads. - library: artist-credit extraction and matching. - lyrics: owned-library lyric search (FTS) with LRCLIB backfill. Also: rewrite README to be user-focused, and migrate upstream to git.ljones.me/yonlu/yellowjacket. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -8,13 +8,14 @@ import '@components/combobox/combobox.ts';
|
||||
|
||||
// ── Field / Operator constants ──────────────────────────────────────
|
||||
|
||||
/** All 16 fields matching the backend `fieldMap` keys. */
|
||||
/** All fields matching the backend `fieldMap` keys. */
|
||||
const FIELDS: string[] = [
|
||||
'title',
|
||||
'artist',
|
||||
'album',
|
||||
'genre',
|
||||
'year',
|
||||
'release_year',
|
||||
'composer',
|
||||
'file_type',
|
||||
'duration',
|
||||
@@ -32,6 +33,7 @@ const FIELDS: string[] = [
|
||||
|
||||
const NUMERIC_FIELDS = new Set([
|
||||
'year',
|
||||
'release_year',
|
||||
'duration',
|
||||
'sample_rate',
|
||||
'bit_depth',
|
||||
@@ -63,7 +65,16 @@ const NUMERIC_OPERATORS = [
|
||||
'between',
|
||||
];
|
||||
|
||||
const SORT_FIELDS = ['title', 'artist', 'album', 'year', 'duration', 'play_count', 'random'];
|
||||
const SORT_FIELDS = [
|
||||
'title',
|
||||
'artist',
|
||||
'album',
|
||||
'year',
|
||||
'release_year',
|
||||
'duration',
|
||||
'play_count',
|
||||
'random',
|
||||
];
|
||||
|
||||
// ── Helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
@@ -103,7 +114,12 @@ function getAutocompleteOptions(field: string): string[] {
|
||||
if (!tracks) return [];
|
||||
return [...new Set(tracks.map((t) => t.FileType).filter(Boolean))];
|
||||
}
|
||||
case 'year': {
|
||||
case 'year':
|
||||
case 'release_year': {
|
||||
// Both year fields draw suggestions from the set of years
|
||||
// present in the library. The cached Track only carries the
|
||||
// display (original) year, so it seeds both datalists — the
|
||||
// list is just a hint, and the real filter runs server-side.
|
||||
const tracks = libraryStore.getCachedTracks();
|
||||
if (!tracks) return [];
|
||||
return [
|
||||
@@ -120,8 +136,21 @@ function getAutocompleteOptions(field: string): string[] {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides for fields whose title-cased name would be ambiguous. The
|
||||
* two year fields in particular need to disambiguate the album's
|
||||
* original release from the specific (possibly reissue) release owned.
|
||||
*/
|
||||
const FIELD_LABEL_OVERRIDES: Record<string, string> = {
|
||||
year: 'Year (Original Release)',
|
||||
release_year: 'Year (This Release)',
|
||||
};
|
||||
|
||||
/** Format a field name for display: `file_type` → "File Type". */
|
||||
function formatFieldLabel(field: string): string {
|
||||
const override = FIELD_LABEL_OVERRIDES[field];
|
||||
if (override) return override;
|
||||
|
||||
return field
|
||||
.split('_')
|
||||
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
||||
|
||||
Reference in New Issue
Block a user