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:
1 parent
d5140395da
commit
65048401e8
117 files changed
+17033
-4767
No files matched your search
@@ -0,0 +1,72 @@
|
||||
package library
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"yellowjacket/backend/metadata"
|
||||
)
|
||||
|
||||
// featuringSeparators are the credit join phrases that introduce a
|
||||
// featured (non-primary) artist. Only true "featuring" markers are
|
||||
// listed: separators like "&", "x", "with", and "," are deliberately
|
||||
// excluded because they routinely appear inside real artist names
|
||||
// (e.g. "Simon & Garfunkel", "Tyler, the Creator").
|
||||
var featuringSeparators = []string{
|
||||
" feat. ", " feat ", " featuring ", " ft. ", " ft ",
|
||||
}
|
||||
|
||||
// stripFeaturing returns the credit up to its first "featuring" marker,
|
||||
// yielding the primary-artist portion of a credit string. "Lana Del
|
||||
// Rey ft. Sean Lennon" becomes "Lana Del Rey"; a credit with no marker
|
||||
// is returned unchanged (trimmed). Matching is case-insensitive.
|
||||
func stripFeaturing(credit string) string {
|
||||
lower := strings.ToLower(credit)
|
||||
|
||||
cut := -1
|
||||
|
||||
for _, sep := range featuringSeparators {
|
||||
if i := strings.Index(lower, sep); i >= 0 && (cut < 0 || i < cut) {
|
||||
cut = i
|
||||
}
|
||||
}
|
||||
|
||||
if cut < 0 {
|
||||
return strings.TrimSpace(credit)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(credit[:cut])
|
||||
}
|
||||
|
||||
// primaryArtist resolves the single canonical artist a track credit
|
||||
// should map to, plus that artist's MusicBrainz ID. A file tags its
|
||||
// ARTIST as a full credit string ("Lana Del Rey ft. Sean Lennon") but
|
||||
// carries only one MUSICBRAINZ_ARTISTID — the primary artist's. Storing
|
||||
// the whole credit as an artist entity, and stamping the primary MBID on
|
||||
// it, is what produced duplicate, mis-titled artists (one MBID fanned
|
||||
// out across many rows); instead we resolve the primary artist's clean
|
||||
// name here and keep the full credit only as the artist_credit text.
|
||||
//
|
||||
// The clean name comes from the album-artist tag when the track resolves
|
||||
// to the same MBID as the album artist (the common "Album Artist feat.
|
||||
// Guest" case, where ALBUMARTIST is the authoritative name). Otherwise
|
||||
// the featured clause is stripped from the credit string.
|
||||
func primaryArtist(tags *metadata.TrackMetadata) (name, mbid string) {
|
||||
mbid = tags.ArtistMBID
|
||||
if mbid == "" {
|
||||
mbid = tags.AlbumArtistMBID
|
||||
}
|
||||
|
||||
if tags.ArtistMBID != "" &&
|
||||
tags.ArtistMBID == tags.AlbumArtistMBID &&
|
||||
tags.AlbumArtist != "" {
|
||||
name = tags.AlbumArtist
|
||||
} else {
|
||||
name = stripFeaturing(tags.Artist)
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
name = "Unknown Artist"
|
||||
}
|
||||
|
||||
return name, mbid
|
||||
}
|
||||
Reference in new issue
Block a user