diff --git a/go.mod b/go.mod index 25ec7c4..02a45f5 100644 --- a/go.mod +++ b/go.mod @@ -23,12 +23,12 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/mmcdole/gofeed v1.2.0 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 - github.com/nicksellen/audiotags v0.0.0-20160226222119-94015fa599bd github.com/oklog/run v1.1.0 github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c github.com/peterbourgon/ff v1.7.1 github.com/philippta/go-template v0.0.0-20220911145045-4556aca435e4 github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be + github.com/sentriz/audiotags v0.0.0-20230419125925-8886243b2137 github.com/sentriz/gormstore v0.0.0-20220105134332-64e31f7f6981 github.com/stretchr/testify v1.8.1 golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 diff --git a/go.sum b/go.sum index 35a25a3..5684655 100644 --- a/go.sum +++ b/go.sum @@ -122,8 +122,6 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= -github.com/nicksellen/audiotags v0.0.0-20160226222119-94015fa599bd h1:xKn/gU8lZupoZt/HE7a/R3aH93iUO6JwyRsYelQUsRI= -github.com/nicksellen/audiotags v0.0.0-20160226222119-94015fa599bd/go.mod h1:B6icauz2l4tkYQxmDtCH4qmNWz/evSW5CsOqp6IE5IE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= @@ -141,6 +139,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q= +github.com/sentriz/audiotags v0.0.0-20230419125925-8886243b2137 h1:K0PSMi/p9ISHpfRFJB03d7VX+jjEsDARsTlAcN1zpac= +github.com/sentriz/audiotags v0.0.0-20230419125925-8886243b2137/go.mod h1:nUVlCJ7n2jQoJ5rttpHozZ8pHJIhD9VehL6GP21FoDU= github.com/sentriz/gormstore v0.0.0-20220105134332-64e31f7f6981 h1:sLILANWN76ja66/K4k/mBqJuCjDZaM67w+Ru6rEB0s0= github.com/sentriz/gormstore v0.0.0-20220105134332-64e31f7f6981/go.mod h1:Rx8XB1ck+so+41uu9VY1gMKs1CPQ2NTq0pzf+OCCQHo= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= diff --git a/mockfs/mockfs.go b/mockfs/mockfs.go index 06d9136..2d10f91 100644 --- a/mockfs/mockfs.go +++ b/mockfs/mockfs.go @@ -351,35 +351,32 @@ func (m *tagReader) Read(abspath string) (tags.Parser, error) { var _ tags.Reader = (*tagReader)(nil) type Tags struct { - RawTitle string - RawArtist string - RawAlbum string - RawAlbumArtist string - RawGenre string + RawTitle string + RawArtist string + RawAlbum string + RawAlbumArtist string + RawAlbumArtists []string + RawGenre string RawBitrate int RawLength int } -func (m *Tags) Title() string { return m.RawTitle } -func (m *Tags) BrainzID() string { return "" } -func (m *Tags) Artist() string { return m.RawArtist } -func (m *Tags) Album() string { return m.RawAlbum } -func (m *Tags) AlbumArtist() string { return m.RawAlbumArtist } -func (m *Tags) AlbumBrainzID() string { return "" } -func (m *Tags) Genre() string { return m.RawGenre } -func (m *Tags) TrackNumber() int { return 1 } -func (m *Tags) DiscNumber() int { return 1 } -func (m *Tags) Year() int { return 2021 } +func (m *Tags) Title() string { return m.RawTitle } +func (m *Tags) BrainzID() string { return "" } +func (m *Tags) Artist() string { return m.RawArtist } +func (m *Tags) Album() string { return m.RawAlbum } +func (m *Tags) AlbumArtist() string { return m.RawAlbumArtist } +func (m *Tags) AlbumArtists() []string { return m.RawAlbumArtists } +func (m *Tags) AlbumBrainzID() string { return "" } +func (m *Tags) Genre() string { return m.RawGenre } +func (m *Tags) TrackNumber() int { return 1 } +func (m *Tags) DiscNumber() int { return 1 } +func (m *Tags) Year() int { return 2021 } func (m *Tags) Length() int { return firstInt(100, m.RawLength) } func (m *Tags) Bitrate() int { return firstInt(100, m.RawBitrate) } -func (m *Tags) SomeAlbum() string { return first("Unknown Album", m.Album()) } -func (m *Tags) SomeArtist() string { return first("Unknown Artist", m.Artist()) } -func (m *Tags) SomeAlbumArtist() string { return first("Unknown Artist", m.AlbumArtist(), m.Artist()) } -func (m *Tags) SomeGenre() string { return first("Unknown Genre", m.Genre()) } - var _ tags.Parser = (*Tags)(nil) func first(or string, strs ...string) string { diff --git a/scanner/scanner.go b/scanner/scanner.go index 6e236f7..0fcb7ce 100644 --- a/scanner/scanner.go +++ b/scanner/scanner.go @@ -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() diff --git a/scanner/tags/tags.go b/scanner/tags/tags.go index 66b3180..b7e910e 100644 --- a/scanner/tags/tags.go +++ b/scanner/tags/tags.go @@ -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" }