feat(tags): support multi valued tags like albumartists
This commit is contained in:
@@ -353,7 +353,7 @@ func (s *Scanner) populateTrackAndAlbumArtists(tx *db.DB, c *Context, i int, par
|
||||
return fmt.Errorf("%v: %w", err, ErrReadingTags)
|
||||
}
|
||||
|
||||
genreNames := strings.Split(trags.SomeGenre(), s.genreSplit)
|
||||
genreNames := strings.Split(tags.MustGenre(trags), s.genreSplit)
|
||||
genreIDs, err := populateGenres(tx, genreNames)
|
||||
if err != nil {
|
||||
return fmt.Errorf("populate genres: %w", err)
|
||||
@@ -361,7 +361,7 @@ func (s *Scanner) populateTrackAndAlbumArtists(tx *db.DB, c *Context, i int, par
|
||||
|
||||
// metadata for the album table comes only from the the first track's tags
|
||||
if i == 0 || album.TagArtist == nil {
|
||||
albumArtist, err := populateAlbumArtist(tx, parent, trags.SomeAlbumArtist())
|
||||
albumArtist, err := populateAlbumArtist(tx, parent, tags.MustAlbumArtist(trags))
|
||||
if err != nil {
|
||||
return fmt.Errorf("populate album artist: %w", err)
|
||||
}
|
||||
@@ -387,7 +387,7 @@ func (s *Scanner) populateTrackAndAlbumArtists(tx *db.DB, c *Context, i int, par
|
||||
}
|
||||
|
||||
func populateAlbum(tx *db.DB, album *db.Album, albumArtist *db.Artist, trags tags.Parser, modTime time.Time) error {
|
||||
albumName := trags.SomeAlbum()
|
||||
albumName := tags.MustAlbum(trags)
|
||||
album.TagTitle = albumName
|
||||
album.TagTitleUDec = decoded(albumName)
|
||||
album.TagBrainzID = trags.AlbumBrainzID()
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/nicksellen/audiotags"
|
||||
"github.com/sentriz/audiotags"
|
||||
)
|
||||
|
||||
type TagReader struct{}
|
||||
@@ -15,49 +15,33 @@ func (*TagReader) Read(abspath string) (Parser, error) {
|
||||
}
|
||||
|
||||
type Tagger struct {
|
||||
raw map[string]string
|
||||
raw map[string][]string
|
||||
props *audiotags.AudioProperties
|
||||
}
|
||||
|
||||
func (t *Tagger) first(keys ...string) string {
|
||||
for _, key := range keys {
|
||||
if v := strings.TrimSpace(t.raw[key]); v != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (t *Tagger) firstInt(sep string, keys ...string) int {
|
||||
for _, key := range keys {
|
||||
if v := intSep(t.raw[key], sep); v > 0 {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// https://picard-docs.musicbrainz.org/downloads/MusicBrainz_Picard_Tag_Map.html
|
||||
|
||||
func (t *Tagger) Title() string { return t.first("title") }
|
||||
func (t *Tagger) BrainzID() string { return t.first("musicbrainz_trackid") } // musicbrainz recording ID
|
||||
func (t *Tagger) Artist() string { return t.first("artist") }
|
||||
func (t *Tagger) Album() string { return t.first("album") }
|
||||
func (t *Tagger) AlbumArtist() string { return t.first("albumartist", "album artist") }
|
||||
func (t *Tagger) AlbumBrainzID() string { return t.first("musicbrainz_albumid") } // musicbrainz release ID
|
||||
func (t *Tagger) Genre() string { return t.first("genre") }
|
||||
func (t *Tagger) TrackNumber() int { return t.firstInt("/" /* eg. 5/12 */, "tracknumber") }
|
||||
func (t *Tagger) DiscNumber() int { return t.firstInt("/" /* eg. 1/2 */, "discnumber") }
|
||||
func (t *Tagger) Length() int { return t.props.Length }
|
||||
func (t *Tagger) Bitrate() int { return t.props.Bitrate }
|
||||
func (t *Tagger) Year() int { return t.firstInt("-", "originaldate", "date", "year") }
|
||||
func (t *Tagger) Title() string { return first(find(t.raw, "title")) }
|
||||
func (t *Tagger) BrainzID() string { return first(find(t.raw, "musicbrainz_trackid")) } // musicbrainz recording ID
|
||||
func (t *Tagger) Artist() string { return first(find(t.raw, "artist")) }
|
||||
func (t *Tagger) Album() string { return first(find(t.raw, "album")) }
|
||||
func (t *Tagger) AlbumArtist() string { return first(find(t.raw, "albumartist", "album artist")) }
|
||||
func (t *Tagger) AlbumArtists() []string { return find(t.raw, "albumartists", "album_artists") }
|
||||
func (t *Tagger) AlbumBrainzID() string { return first(find(t.raw, "musicbrainz_albumid")) } // musicbrainz release ID
|
||||
func (t *Tagger) Genre() string { return first(find(t.raw, "genre")) }
|
||||
|
||||
func (t *Tagger) SomeAlbum() string { return first("Unknown Album", t.Album()) }
|
||||
func (t *Tagger) SomeArtist() string { return first("Unknown Artist", t.Artist()) }
|
||||
func (t *Tagger) SomeAlbumArtist() string {
|
||||
return first("Unknown Artist", t.AlbumArtist(), t.Artist())
|
||||
func (t *Tagger) TrackNumber() int {
|
||||
return intSep("/" /* eg. 5/12 */, first(find(t.raw, "tracknumber")))
|
||||
}
|
||||
func (t *Tagger) SomeGenre() string { return first("Unknown Genre", t.Genre()) }
|
||||
func (t *Tagger) DiscNumber() int {
|
||||
return intSep("/" /* eg. 1/2 */, first(find(t.raw, "discnumber")))
|
||||
}
|
||||
func (t *Tagger) Year() int {
|
||||
return intSep("-" /* 2023-12-01 */, first(find(t.raw, "originaldate", "date", "year")))
|
||||
}
|
||||
|
||||
func (t *Tagger) Length() int { return t.props.Length }
|
||||
func (t *Tagger) Bitrate() int { return t.props.Bitrate }
|
||||
|
||||
type Reader interface {
|
||||
Read(abspath string) (Parser, error)
|
||||
@@ -69,6 +53,7 @@ type Parser interface {
|
||||
Artist() string
|
||||
Album() string
|
||||
AlbumArtist() string
|
||||
AlbumArtists() []string
|
||||
AlbumBrainzID() string
|
||||
Genre() string
|
||||
TrackNumber() int
|
||||
@@ -76,11 +61,44 @@ type Parser interface {
|
||||
Length() int
|
||||
Bitrate() int
|
||||
Year() int
|
||||
}
|
||||
|
||||
SomeAlbum() string
|
||||
SomeArtist() string
|
||||
SomeAlbumArtist() string
|
||||
SomeGenre() string
|
||||
func fallback(or string, strs ...string) string {
|
||||
for _, str := range strs {
|
||||
if str != "" {
|
||||
return str
|
||||
}
|
||||
}
|
||||
return or
|
||||
}
|
||||
|
||||
func first[T comparable](is []T) T {
|
||||
var z T
|
||||
for _, i := range is {
|
||||
if i != z {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return z
|
||||
}
|
||||
|
||||
func find(m map[string][]string, keys ...string) []string {
|
||||
for _, k := range keys {
|
||||
if r := filterStr(m[k]); len(r) > 0 {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func filterStr(ss []string) []string {
|
||||
var r []string
|
||||
for _, s := range ss {
|
||||
if strings.TrimSpace(s) != "" {
|
||||
r = append(r, s)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func intSep(in, sep string) int {
|
||||
@@ -95,11 +113,46 @@ func intSep(in, sep string) int {
|
||||
return out
|
||||
}
|
||||
|
||||
func first(or string, strs ...string) string {
|
||||
for _, str := range strs {
|
||||
if str != "" {
|
||||
return str
|
||||
}
|
||||
func MustAlbum(p Parser) string {
|
||||
if r := p.Album(); r != "" {
|
||||
return r
|
||||
}
|
||||
return or
|
||||
return "Unknown Album"
|
||||
}
|
||||
|
||||
func MustArtist(p Parser) string {
|
||||
if r := p.Artist(); r != "" {
|
||||
return r
|
||||
}
|
||||
return "Unknown Artist"
|
||||
}
|
||||
|
||||
func MustAlbumArtist(p Parser) string {
|
||||
if r := p.AlbumArtist(); r != "" {
|
||||
return r
|
||||
}
|
||||
if r := p.Artist(); r != "" {
|
||||
return r
|
||||
}
|
||||
return "Unknown Artist"
|
||||
}
|
||||
|
||||
func MustAlbumArtists(p Parser) []string {
|
||||
if r := p.AlbumArtists(); len(r) > 0 {
|
||||
return r
|
||||
}
|
||||
if r := p.AlbumArtist(); r != "" {
|
||||
return []string{r}
|
||||
}
|
||||
if r := p.Artist(); r != "" {
|
||||
return []string{r}
|
||||
}
|
||||
return []string{"Unknown Artist"}
|
||||
}
|
||||
|
||||
func MustGenre(p Parser) string {
|
||||
if r := p.Genre(); r != "" {
|
||||
return r
|
||||
}
|
||||
return "Unknown Genre"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user