diff --git a/scanner/metadata/ffmpeg/ffmpeg.go b/scanner/metadata/ffmpeg/ffmpeg.go index d26e8f5f..c8d47023 100644 --- a/scanner/metadata/ffmpeg/ffmpeg.go +++ b/scanner/metadata/ffmpeg/ffmpeg.go @@ -39,6 +39,13 @@ func (e *Extractor) Parse(files ...string) (map[string]metadata.ParsedTags, erro return fileTags, nil } +func (e *Extractor) CustomMappings() metadata.ParsedTags { + return metadata.ParsedTags{ + "disc": {"tpa"}, + "has_picture": {"metadata_block_picture"}, + } +} + func (e *Extractor) extractMetadata(filePath, info string) (metadata.ParsedTags, error) { tags := e.parseInfo(info) if len(tags) == 0 { @@ -46,17 +53,6 @@ func (e *Extractor) extractMetadata(filePath, info string) (metadata.ParsedTags, return nil, errors.New("not a media file") } - alternativeTags := map[string][]string{ - "disc": {"tpa"}, - "has_picture": {"metadata_block_picture"}, - } - for tagName, alternatives := range alternativeTags { - for _, altName := range alternatives { - if altValue, ok := tags[altName]; ok { - tags[tagName] = append(tags[tagName], altValue...) - } - } - } return tags, nil } @@ -186,17 +182,18 @@ func (e *Extractor) parseDuration(tag string) string { } func (e *Extractor) parseChannels(tag string) string { - if tag == "mono" { + switch tag { + case "mono": return "1" - } else if tag == "stereo" { + case "stereo": return "2" - } else if tag == "5.1" { + case "5.1": return "6" - } else if tag == "7.1" { + case "7.1": return "8" + default: + return "0" } - - return "0" } // Inputs will always be absolute paths diff --git a/scanner/metadata/ffmpeg/ffmpeg_test.go b/scanner/metadata/ffmpeg/ffmpeg_test.go index 71fe6169..94ad196c 100644 --- a/scanner/metadata/ffmpeg/ffmpeg_test.go +++ b/scanner/metadata/ffmpeg/ffmpeg_test.go @@ -77,6 +77,8 @@ Input #0, ogg, from '/Users/deluan/Music/iTunes/iTunes Media/Music/_Testes/Jamai metadata_block_picture: AAAAAwAAAAppbWFnZS9qcGVnAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4Id/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQ TITLE : Jamaican In New York (Album Version)` md, _ := e.extractMetadata("tests/fixtures/test.mp3", output) + Expect(md).To(HaveKey("metadata_block_picture")) + md = md.Map(e.CustomMappings()) Expect(md).To(HaveKey("has_picture")) }) diff --git a/scanner/metadata/metadata.go b/scanner/metadata/metadata.go index cf6caea4..0e9d3dae 100644 --- a/scanner/metadata/metadata.go +++ b/scanner/metadata/metadata.go @@ -16,10 +16,9 @@ import ( "github.com/navidrome/navidrome/log" ) -type ParsedTags = map[string][]string - type Extractor interface { Parse(files ...string) (map[string]ParsedTags, error) + CustomMappings() ParsedTags } var extractors = map[string]Extractor{} @@ -49,6 +48,7 @@ func Extract(files ...string) (map[string]Tags, error) { continue } + tags = tags.Map(p.CustomMappings()) result[filePath] = Tags{ filePath: filePath, fileInfo: fileInfo, @@ -59,10 +59,27 @@ func Extract(files ...string) (map[string]Tags, error) { return result, nil } +type ParsedTags map[string][]string + +func (p ParsedTags) Map(customMappings ParsedTags) ParsedTags { + if customMappings == nil { + return p + } + for tagName, alternatives := range customMappings { + for _, altName := range alternatives { + if altValue, ok := p[altName]; ok { + p[tagName] = append(p[tagName], altValue...) + delete(p, altName) + } + } + } + return p +} + type Tags struct { filePath string fileInfo os.FileInfo - tags map[string][]string + tags ParsedTags } // Common tags @@ -92,6 +109,7 @@ func (t Tags) Bpm() int { return (int)(math.Round(t.getFloat("tbpm", " func (t Tags) HasPicture() bool { return t.getFirstTagValue("has_picture") != "" } // MusicBrainz Identifiers + func (t Tags) MbzReleaseTrackID() string { return t.getMbzID("musicbrainz_releasetrackid", "musicbrainz release track id") } @@ -148,10 +166,10 @@ func (t Tags) getAllTagValues(tagNames ...string) []string { return values } -func (t Tags) getSortTag(originalTag string, tagNamess ...string) string { +func (t Tags) getSortTag(originalTag string, tagNames ...string) string { formats := []string{"sort%s", "sort_%s", "sort-%s", "%ssort", "%s_sort", "%s-sort"} all := []string{originalTag} - for _, tag := range tagNamess { + for _, tag := range tagNames { for _, format := range formats { name := fmt.Sprintf(format, tag) all = append(all, name) diff --git a/scanner/metadata/metadata_internal_test.go b/scanner/metadata/metadata_internal_test.go new file mode 100644 index 00000000..6a10ecdf --- /dev/null +++ b/scanner/metadata/metadata_internal_test.go @@ -0,0 +1,89 @@ +package metadata + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Tags", func() { + Describe("getYear", func() { + It("parses the year correctly", func() { + var examples = map[string]int{ + "1985": 1985, + "2002-01": 2002, + "1969.06": 1969, + "1980.07.25": 1980, + "2004-00-00": 2004, + "2013-May-12": 2013, + "May 12, 2016": 2016, + "01/10/1990": 1990, + } + for tag, expected := range examples { + md := &Tags{} + md.tags = map[string][]string{"date": {tag}} + Expect(md.Year()).To(Equal(expected)) + } + }) + + It("returns 0 if year is invalid", func() { + md := &Tags{} + md.tags = map[string][]string{"date": {"invalid"}} + Expect(md.Year()).To(Equal(0)) + }) + }) + + Describe("getMbzID", func() { + It("return a valid MBID", func() { + md := &Tags{} + md.tags = map[string][]string{ + "musicbrainz_trackid": {"8f84da07-09a0-477b-b216-cc982dabcde1"}, + "musicbrainz_releasetrackid": {"6caf16d3-0b20-3fe6-8020-52e31831bc11"}, + "musicbrainz_albumid": {"f68c985d-f18b-4f4a-b7f0-87837cf3fbf9"}, + "musicbrainz_artistid": {"89ad4ac3-39f7-470e-963a-56509c546377"}, + "musicbrainz_albumartistid": {"ada7a83c-e3e1-40f1-93f9-3e73dbc9298a"}, + } + Expect(md.MbzTrackID()).To(Equal("8f84da07-09a0-477b-b216-cc982dabcde1")) + Expect(md.MbzReleaseTrackID()).To(Equal("6caf16d3-0b20-3fe6-8020-52e31831bc11")) + Expect(md.MbzAlbumID()).To(Equal("f68c985d-f18b-4f4a-b7f0-87837cf3fbf9")) + Expect(md.MbzArtistID()).To(Equal("89ad4ac3-39f7-470e-963a-56509c546377")) + Expect(md.MbzAlbumArtistID()).To(Equal("ada7a83c-e3e1-40f1-93f9-3e73dbc9298a")) + }) + It("return empty string for invalid MBID", func() { + md := &Tags{} + md.tags = map[string][]string{ + "musicbrainz_trackid": {"11406732-6"}, + "musicbrainz_albumid": {"11406732"}, + "musicbrainz_artistid": {"200455"}, + "musicbrainz_albumartistid": {"194"}, + } + Expect(md.MbzTrackID()).To(Equal("")) + Expect(md.MbzAlbumID()).To(Equal("")) + Expect(md.MbzArtistID()).To(Equal("")) + Expect(md.MbzAlbumArtistID()).To(Equal("")) + }) + }) + + Describe("getAllTagValues", func() { + It("returns values from all tag names", func() { + md := &Tags{} + md.tags = map[string][]string{ + "genre": {"Rock", "Pop", "New Wave"}, + } + + Expect(md.Genres()).To(ConsistOf("Rock", "Pop", "New Wave")) + }) + }) + + Describe("Bpm", func() { + var t *Tags + BeforeEach(func() { + t = &Tags{tags: map[string][]string{ + "fbpm": []string{"141.7"}, + }} + }) + + It("rounds a floating point fBPM tag", func() { + Expect(t.Bpm()).To(Equal(142)) + }) + }) +}) diff --git a/scanner/metadata/metadata_test.go b/scanner/metadata/metadata_test.go index 9b66de9e..06165400 100644 --- a/scanner/metadata/metadata_test.go +++ b/scanner/metadata/metadata_test.go @@ -1,7 +1,10 @@ -package metadata +package metadata_test import ( "github.com/navidrome/navidrome/conf" + "github.com/navidrome/navidrome/scanner/metadata" + _ "github.com/navidrome/navidrome/scanner/metadata/ffmpeg" + _ "github.com/navidrome/navidrome/scanner/metadata/taglib" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -13,7 +16,7 @@ var _ = Describe("Tags", func() { }) It("correctly parses metadata from all files in folder", func() { - mds, err := Extract("tests/fixtures/test.mp3", "tests/fixtures/test.ogg") + mds, err := metadata.Extract("tests/fixtures/test.mp3", "tests/fixtures/test.ogg") Expect(err).NotTo(HaveOccurred()) Expect(mds).To(HaveLen(2)) @@ -52,85 +55,4 @@ var _ = Describe("Tags", func() { Expect(m.BitRate()).To(BeElementOf(18, 39)) }) }) - - Describe("getYear", func() { - It("parses the year correctly", func() { - var examples = map[string]int{ - "1985": 1985, - "2002-01": 2002, - "1969.06": 1969, - "1980.07.25": 1980, - "2004-00-00": 2004, - "2013-May-12": 2013, - "May 12, 2016": 2016, - "01/10/1990": 1990, - } - for tag, expected := range examples { - md := &Tags{} - md.tags = map[string][]string{"date": {tag}} - Expect(md.Year()).To(Equal(expected)) - } - }) - - It("returns 0 if year is invalid", func() { - md := &Tags{} - md.tags = map[string][]string{"date": {"invalid"}} - Expect(md.Year()).To(Equal(0)) - }) - }) - - Describe("getMbzID", func() { - It("return a valid MBID", func() { - md := &Tags{} - md.tags = map[string][]string{ - "musicbrainz_trackid": {"8f84da07-09a0-477b-b216-cc982dabcde1"}, - "musicbrainz_releasetrackid": {"6caf16d3-0b20-3fe6-8020-52e31831bc11"}, - "musicbrainz_albumid": {"f68c985d-f18b-4f4a-b7f0-87837cf3fbf9"}, - "musicbrainz_artistid": {"89ad4ac3-39f7-470e-963a-56509c546377"}, - "musicbrainz_albumartistid": {"ada7a83c-e3e1-40f1-93f9-3e73dbc9298a"}, - } - Expect(md.MbzTrackID()).To(Equal("8f84da07-09a0-477b-b216-cc982dabcde1")) - Expect(md.MbzReleaseTrackID()).To(Equal("6caf16d3-0b20-3fe6-8020-52e31831bc11")) - Expect(md.MbzAlbumID()).To(Equal("f68c985d-f18b-4f4a-b7f0-87837cf3fbf9")) - Expect(md.MbzArtistID()).To(Equal("89ad4ac3-39f7-470e-963a-56509c546377")) - Expect(md.MbzAlbumArtistID()).To(Equal("ada7a83c-e3e1-40f1-93f9-3e73dbc9298a")) - }) - It("return empty string for invalid MBID", func() { - md := &Tags{} - md.tags = map[string][]string{ - "musicbrainz_trackid": {"11406732-6"}, - "musicbrainz_albumid": {"11406732"}, - "musicbrainz_artistid": {"200455"}, - "musicbrainz_albumartistid": {"194"}, - } - Expect(md.MbzTrackID()).To(Equal("")) - Expect(md.MbzAlbumID()).To(Equal("")) - Expect(md.MbzArtistID()).To(Equal("")) - Expect(md.MbzAlbumArtistID()).To(Equal("")) - }) - }) - - Describe("getAllTagValues", func() { - It("returns values from all tag names", func() { - md := &Tags{} - md.tags = map[string][]string{ - "genre": {"Rock", "Pop", "New Wave"}, - } - - Expect(md.Genres()).To(ConsistOf("Rock", "Pop", "New Wave")) - }) - }) - - Describe("Bpm", func() { - var t *Tags - BeforeEach(func() { - t = &Tags{tags: map[string][]string{ - "fbpm": []string{"141.7"}, - }} - }) - - It("rounds a floating point fBPM tag", func() { - Expect(t.Bpm()).To(Equal(142)) - }) - }) }) diff --git a/scanner/metadata/taglib/taglib.go b/scanner/metadata/taglib/taglib.go index 76417162..0a2aa009 100644 --- a/scanner/metadata/taglib/taglib.go +++ b/scanner/metadata/taglib/taglib.go @@ -24,6 +24,15 @@ func (e *Extractor) Parse(paths ...string) (map[string]metadata.ParsedTags, erro return fileTags, nil } +func (e *Extractor) CustomMappings() metadata.ParsedTags { + return metadata.ParsedTags{ + "title": {"titlesort"}, + "album": {"albumsort"}, + "artist": {"artistsort"}, + "tracknumber": {"trck", "_track"}, + } +} + func (e *Extractor) extractMetadata(filePath string) (metadata.ParsedTags, error) { tags, err := Read(filePath) if err != nil { @@ -31,13 +40,6 @@ func (e *Extractor) extractMetadata(filePath string) (metadata.ParsedTags, error return nil, err } - alternativeTags := map[string][]string{ - "title": {"titlesort"}, - "album": {"albumsort"}, - "artist": {"artistsort"}, - "tracknumber": {"trck", "_track"}, - } - if length, ok := tags["lengthinmilliseconds"]; ok && len(length) > 0 { millis, _ := strconv.Atoi(length[0]) if duration := float64(millis) / 1000.0; duration > 0 { @@ -45,13 +47,6 @@ func (e *Extractor) extractMetadata(filePath string) (metadata.ParsedTags, error } } - for tagName, alternatives := range alternativeTags { - for _, altName := range alternatives { - if altValue, ok := tags[altName]; ok { - tags[tagName] = append(tags[tagName], altValue...) - } - } - } return tags, nil } diff --git a/scanner/metadata/taglib/taglib_test.go b/scanner/metadata/taglib/taglib_test.go index b5308edb..14ff223e 100644 --- a/scanner/metadata/taglib/taglib_test.go +++ b/scanner/metadata/taglib/taglib_test.go @@ -42,7 +42,6 @@ var _ = Describe("Extractor", func() { Expect(m).To(HaveKeyWithValue("tcmp", []string{"1"})) // Compilation Expect(m).To(HaveKeyWithValue("genre", []string{"Rock"})) Expect(m).To(HaveKeyWithValue("date", []string{"2014", "2014"})) - Expect(m).To(HaveKeyWithValue("tracknumber", []string{"2/10", "2/10", "2"})) Expect(m).To(HaveKeyWithValue("discnumber", []string{"1/2"})) Expect(m).To(HaveKeyWithValue("has_picture", []string{"true"})) Expect(m).To(HaveKeyWithValue("duration", []string{"1.02"})) @@ -52,6 +51,10 @@ var _ = Describe("Extractor", func() { Expect(m).To(HaveKeyWithValue("lyrics", []string{"Lyrics 1\rLyrics 2"})) Expect(m).To(HaveKeyWithValue("bpm", []string{"123"})) + Expect(m).To(HaveKeyWithValue("tracknumber", []string{"2/10"})) + m = m.Map(e.CustomMappings()) + Expect(m).To(HaveKeyWithValue("tracknumber", []string{"2/10", "2/10", "2"})) + m = mds["tests/fixtures/test.ogg"] Expect(err).To(BeNil()) Expect(m).ToNot(HaveKey("title"))