changed genre scanning to parse multiple genres, updated db to accommodate.

This commit is contained in:
2026-02-22 13:59:28 -05:00
parent 3947c2d6aa
commit 9cb1c6674a
24 changed files with 457 additions and 40 deletions
+67 -1
View File
@@ -43,6 +43,7 @@ type entityCache struct {
artists map[string]sqlcgen.Artist
releaseGroups map[string]sqlcgen.ReleaseGroup
coverArt map[string]sqlcgen.CoverArt
genres map[string]sqlcgen.Genre
// linkedCredits tracks artist-credit-artist links already created
// so we skip the duplicate INSERT. Key is "artistID:creditID".
linkedCredits map[string]struct{}
@@ -54,6 +55,7 @@ func newEntityCache() *entityCache {
artists: make(map[string]sqlcgen.Artist),
releaseGroups: make(map[string]sqlcgen.ReleaseGroup),
coverArt: make(map[string]sqlcgen.CoverArt),
genres: make(map[string]sqlcgen.Genre),
linkedCredits: make(map[string]struct{}),
}
}
@@ -856,7 +858,10 @@ func (l *Library) processMetadata(
)
}
// 6. Link recording to release group.
// 6. Link recording to genres.
l.linkRecordingGenres(q, cache, tags.Genre, recording.ID)
// 7. Link recording to release group.
if releaseGroupID.Valid {
_, err = q.CreateReleaseGroupRecording(
l.ctx,
@@ -990,6 +995,67 @@ func (l *Library) cachedLinkArtist(
cache.linkedCredits[linkKey] = struct{}{}
}
// cachedUpsertGenre returns the genre for the given name, using
// the cache when possible.
func (l *Library) cachedUpsertGenre(
q *sqlcgen.Queries,
cache *entityCache,
name string,
) (sqlcgen.Genre, error) {
if cached, ok := cache.genres[name]; ok {
return cached, nil
}
genre, err := q.UpsertGenre(l.ctx, name)
if err != nil {
return sqlcgen.Genre{}, err
}
cache.genres[name] = genre
return genre, nil
}
// linkRecordingGenres parses the raw genre string, upserts each
// individual genre, and creates the recording-genre associations.
func (l *Library) linkRecordingGenres(
q *sqlcgen.Queries,
cache *entityCache,
rawGenre string,
recordingID int64,
) {
genres := metadata.ParseGenres(rawGenre)
for _, name := range genres {
genre, err := l.cachedUpsertGenre(q, cache, name)
if err != nil {
l.logger.Warn(
"could not upsert genre",
"genre", name,
"err", err,
)
continue
}
err = q.CreateRecordingGenre(
l.ctx,
sqlcgen.CreateRecordingGenreParams{
RecordingID: recordingID,
GenreID: genre.ID,
},
)
if err != nil {
l.logger.Warn(
"could not link recording to genre",
"genre", name,
"recordingID", recordingID,
"err", err,
)
}
}
}
// resolveAlbumArtistCredit returns the album artist credit ID.
// When the AlbumArtist tag is absent or matches the track artist,
// the track artist credit is reused.
+17 -2
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"path/filepath"
"strconv"
"strings"
)
// Sentinel errors for library queries.
@@ -22,12 +23,26 @@ type Track struct {
TrackNumber int64
DiscNumber int64
Album string
Genre string
Genre []string
Year int64
Composer string
FileType string
}
// genreDelimiter is the separator used by GROUP_CONCAT in the
// GetAllTracksWithFullMetadata query.
const genreDelimiter = "||"
// splitGenres splits a GROUP_CONCAT genre string into individual
// genre names. An empty string returns nil.
func splitGenres(concatenated string) []string {
if concatenated == "" {
return nil
}
return strings.Split(concatenated, genreDelimiter)
}
// Album represents an album for the cover grid display.
type Album struct {
ID int64
@@ -75,7 +90,7 @@ func (l *Library) GetAllTracks() ([]Track, error) {
TrackNumber: row.TrackNumber.Int64,
DiscNumber: row.DiscNumber.Int64,
Album: row.Album,
Genre: row.Genre,
Genre: splitGenres(row.Genre),
Year: row.Year,
Composer: row.Composer,
FileType: row.FileType,
+12
View File
@@ -102,6 +102,12 @@ func (l *Library) clearLibraryTables() error {
)
}
if err := txq.DeleteAllRecordingGenres(l.ctx); err != nil {
return fmt.Errorf(
"could not clear recording genres: %w", err,
)
}
if err := txq.DeleteAllReleaseGroupRecordings(l.ctx); err != nil {
return fmt.Errorf(
"could not clear release group recordings: %w", err,
@@ -152,6 +158,12 @@ func (l *Library) clearLibraryTables() error {
)
}
if err := txq.DeleteAllGenres(l.ctx); err != nil {
return fmt.Errorf(
"could not clear genres: %w", err,
)
}
if err := tx.Commit(); err != nil {
return fmt.Errorf(
"could not commit library clear transaction: %w", err,