Reorganize metadata extractors code
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
package metadata
|
package ffmpeg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@@ -13,15 +13,17 @@ import (
|
|||||||
"github.com/navidrome/navidrome/log"
|
"github.com/navidrome/navidrome/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ffmpegExtractor struct{}
|
type Parser struct{}
|
||||||
|
|
||||||
func (e *ffmpegExtractor) Extract(files ...string) (map[string]*Tags, error) {
|
type parsedTags = map[string][]string
|
||||||
|
|
||||||
|
func (e *Parser) Parse(files ...string) (map[string]parsedTags, error) {
|
||||||
args := e.createProbeCommand(files)
|
args := e.createProbeCommand(files)
|
||||||
|
|
||||||
log.Trace("Executing command", "args", args)
|
log.Trace("Executing command", "args", args)
|
||||||
cmd := exec.Command(args[0], args[1:]...) // #nosec
|
cmd := exec.Command(args[0], args[1:]...) // #nosec
|
||||||
output, _ := cmd.CombinedOutput()
|
output, _ := cmd.CombinedOutput()
|
||||||
fileTags := map[string]*Tags{}
|
fileTags := map[string]parsedTags{}
|
||||||
if len(output) == 0 {
|
if len(output) == 0 {
|
||||||
return fileTags, errors.New("error extracting metadata files")
|
return fileTags, errors.New("error extracting metadata files")
|
||||||
}
|
}
|
||||||
@@ -36,6 +38,27 @@ func (e *ffmpegExtractor) Extract(files ...string) (map[string]*Tags, error) {
|
|||||||
return fileTags, nil
|
return fileTags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Parser) extractMetadata(filePath, info string) (parsedTags, error) {
|
||||||
|
tags := e.parseInfo(info)
|
||||||
|
if len(tags) == 0 {
|
||||||
|
log.Trace("Not a media file. Skipping", "filePath", filePath)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Input #0, mp3, from 'groovin.mp3':
|
// Input #0, mp3, from 'groovin.mp3':
|
||||||
inputRegex = regexp.MustCompile(`(?m)^Input #\d+,.*,\sfrom\s'(.*)'`)
|
inputRegex = regexp.MustCompile(`(?m)^Input #\d+,.*,\sfrom\s'(.*)'`)
|
||||||
@@ -56,7 +79,7 @@ var (
|
|||||||
coverRx = regexp.MustCompile(`^\s{2,4}Stream #\d+:\d+: (Video):.*`)
|
coverRx = regexp.MustCompile(`^\s{2,4}Stream #\d+:\d+: (Video):.*`)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (e *ffmpegExtractor) parseOutput(output string) map[string]string {
|
func (e *Parser) parseOutput(output string) map[string]string {
|
||||||
outputs := map[string]string{}
|
outputs := map[string]string{}
|
||||||
all := inputRegex.FindAllStringSubmatchIndex(output, -1)
|
all := inputRegex.FindAllStringSubmatchIndex(output, -1)
|
||||||
for i, loc := range all {
|
for i, loc := range all {
|
||||||
@@ -78,21 +101,7 @@ func (e *ffmpegExtractor) parseOutput(output string) map[string]string {
|
|||||||
return outputs
|
return outputs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ffmpegExtractor) extractMetadata(filePath, info string) (*Tags, error) {
|
func (e *Parser) parseInfo(info string) map[string][]string {
|
||||||
parsedTags := e.parseInfo(info)
|
|
||||||
if len(parsedTags) == 0 {
|
|
||||||
log.Trace("Not a media file. Skipping", "filePath", filePath)
|
|
||||||
return nil, errors.New("not a media file")
|
|
||||||
}
|
|
||||||
|
|
||||||
tags := NewTags(filePath, parsedTags, map[string][]string{
|
|
||||||
"disc": {"tpa"},
|
|
||||||
"has_picture": {"metadata_block_picture"},
|
|
||||||
})
|
|
||||||
return tags, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ffmpegExtractor) parseInfo(info string) map[string][]string {
|
|
||||||
tags := map[string][]string{}
|
tags := map[string][]string{}
|
||||||
|
|
||||||
reader := strings.NewReader(info)
|
reader := strings.NewReader(info)
|
||||||
@@ -158,7 +167,7 @@ func (e *ffmpegExtractor) parseInfo(info string) map[string][]string {
|
|||||||
|
|
||||||
var zeroTime = time.Date(0000, time.January, 1, 0, 0, 0, 0, time.UTC)
|
var zeroTime = time.Date(0000, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
func (e *ffmpegExtractor) parseDuration(tag string) string {
|
func (e *Parser) parseDuration(tag string) string {
|
||||||
d, err := time.Parse("15:04:05", tag)
|
d, err := time.Parse("15:04:05", tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "0"
|
return "0"
|
||||||
@@ -167,7 +176,7 @@ func (e *ffmpegExtractor) parseDuration(tag string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Inputs will always be absolute paths
|
// Inputs will always be absolute paths
|
||||||
func (e *ffmpegExtractor) createProbeCommand(inputs []string) []string {
|
func (e *Parser) createProbeCommand(inputs []string) []string {
|
||||||
split := strings.Split(conf.Server.ProbeCommand, " ")
|
split := strings.Split(conf.Server.ProbeCommand, " ")
|
||||||
args := make([]string, 0)
|
args := make([]string, 0)
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package ffmpeg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/navidrome/navidrome/log"
|
||||||
|
"github.com/navidrome/navidrome/tests"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFFMpeg(t *testing.T) {
|
||||||
|
tests.Init(t, true)
|
||||||
|
log.SetLevel(log.LevelCritical)
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "FFMpeg Suite")
|
||||||
|
}
|
||||||
@@ -1,53 +1,14 @@
|
|||||||
package metadata
|
package ffmpeg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("ffmpegExtractor", func() {
|
var _ = Describe("Parser", func() {
|
||||||
var e *ffmpegExtractor
|
var e *Parser
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
e = &ffmpegExtractor{}
|
e = &Parser{}
|
||||||
})
|
|
||||||
// TODO Need to mock `ffmpeg`
|
|
||||||
XContext("Extract", func() {
|
|
||||||
It("correctly parses metadata from all files in folder", func() {
|
|
||||||
mds, err := e.Extract("tests/fixtures/test.mp3", "tests/fixtures/test.ogg")
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(mds).To(HaveLen(2))
|
|
||||||
|
|
||||||
m := mds["tests/fixtures/test.mp3"]
|
|
||||||
Expect(m.Title()).To(Equal("Song"))
|
|
||||||
Expect(m.Album()).To(Equal("Album"))
|
|
||||||
Expect(m.Artist()).To(Equal("Artist"))
|
|
||||||
Expect(m.AlbumArtist()).To(Equal("Album Artist"))
|
|
||||||
Expect(m.Compilation()).To(BeTrue())
|
|
||||||
Expect(m.Genres()).To(Equal("Rock"))
|
|
||||||
Expect(m.Year()).To(Equal(2014))
|
|
||||||
n, t := m.TrackNumber()
|
|
||||||
Expect(n).To(Equal(2))
|
|
||||||
Expect(t).To(Equal(10))
|
|
||||||
n, t = m.DiscNumber()
|
|
||||||
Expect(n).To(Equal(1))
|
|
||||||
Expect(t).To(Equal(2))
|
|
||||||
Expect(m.HasPicture()).To(BeTrue())
|
|
||||||
Expect(m.Duration()).To(BeNumerically("~", 1.03, 0.001))
|
|
||||||
Expect(m.BitRate()).To(Equal(192))
|
|
||||||
Expect(m.FilePath()).To(Equal("tests/fixtures/test.mp3"))
|
|
||||||
Expect(m.Suffix()).To(Equal("mp3"))
|
|
||||||
Expect(m.Size()).To(Equal(int64(51876)))
|
|
||||||
|
|
||||||
m = mds["tests/fixtures/test.ogg"]
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
Expect(m.Title()).To(BeEmpty())
|
|
||||||
Expect(m.HasPicture()).To(BeFalse())
|
|
||||||
Expect(m.Duration()).To(BeNumerically("~", 1.04, 0.001))
|
|
||||||
Expect(m.BitRate()).To(Equal(16))
|
|
||||||
Expect(m.Suffix()).To(Equal("ogg"))
|
|
||||||
Expect(m.FilePath()).To(Equal("tests/fixtures/test.ogg"))
|
|
||||||
Expect(m.Size()).To(Equal(int64(5065)))
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("extractMetadata", func() {
|
Context("extractMetadata", func() {
|
||||||
@@ -70,13 +31,13 @@ Input #0, ape, from './Capture/02 01 - Symphony No. 5 in C minor, Op. 67 I. Alle
|
|||||||
CatalogNumber : PLD 1201
|
CatalogNumber : PLD 1201
|
||||||
`
|
`
|
||||||
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
||||||
Expect(md.CatalogNum()).To(Equal("PLD 1201"))
|
Expect(md).To(HaveKeyWithValue("catalognumber", []string{"PLD 1201"}))
|
||||||
Expect(md.MbzTrackID()).To(Equal("ffe06940-727a-415a-b608-b7e45737f9d8"))
|
Expect(md).To(HaveKeyWithValue("musicbrainz_trackid", []string{"ffe06940-727a-415a-b608-b7e45737f9d8"}))
|
||||||
Expect(md.MbzAlbumID()).To(Equal("71eb5e4a-90e2-4a31-a2d1-a96485fcb667"))
|
Expect(md).To(HaveKeyWithValue("musicbrainz_albumid", []string{"71eb5e4a-90e2-4a31-a2d1-a96485fcb667"}))
|
||||||
Expect(md.MbzArtistID()).To(Equal("1f9df192-a621-4f54-8850-2c5373b7eac9"))
|
Expect(md).To(HaveKeyWithValue("musicbrainz_artistid", []string{"1f9df192-a621-4f54-8850-2c5373b7eac9"}))
|
||||||
Expect(md.MbzAlbumArtistID()).To(Equal("89ad4ac3-39f7-470e-963a-56509c546377"))
|
Expect(md).To(HaveKeyWithValue("musicbrainz_albumartistid", []string{"89ad4ac3-39f7-470e-963a-56509c546377"}))
|
||||||
Expect(md.MbzAlbumType()).To(Equal("album"))
|
Expect(md).To(HaveKeyWithValue("musicbrainz_albumtype", []string{"album"}))
|
||||||
Expect(md.MbzAlbumComment()).To(Equal("MP3"))
|
Expect(md).To(HaveKeyWithValue("musicbrainz_albumcomment", []string{"MP3"}))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("detects embedded cover art correctly", func() {
|
It("detects embedded cover art correctly", func() {
|
||||||
@@ -88,7 +49,7 @@ Input #0, mp3, from '/Users/deluan/Music/iTunes/iTunes Media/Music/Compilations/
|
|||||||
Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 192 kb/s
|
Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 192 kb/s
|
||||||
Stream #0:1: Video: mjpeg, yuvj444p(pc, bt470bg/unknown/unknown), 600x600 [SAR 1:1 DAR 1:1], 90k tbr, 90k tbn, 90k tbc`
|
Stream #0:1: Video: mjpeg, yuvj444p(pc, bt470bg/unknown/unknown), 600x600 [SAR 1:1 DAR 1:1], 90k tbr, 90k tbn, 90k tbc`
|
||||||
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
||||||
Expect(md.HasPicture()).To(BeTrue())
|
Expect(md).To(HaveKeyWithValue("has_picture", []string{"true"}))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("detects embedded cover art in ffmpeg 4.4 output", func() {
|
It("detects embedded cover art in ffmpeg 4.4 output", func() {
|
||||||
@@ -103,7 +64,7 @@ Input #0, flac, from '/run/media/naomi/Archivio/Musica/Katy Perry/Chained to the
|
|||||||
Metadata:
|
Metadata:
|
||||||
comment : Cover (front)`
|
comment : Cover (front)`
|
||||||
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
||||||
Expect(md.HasPicture()).To(BeTrue())
|
Expect(md).To(HaveKeyWithValue("has_picture", []string{"true"}))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("detects embedded cover art in ogg containers", func() {
|
It("detects embedded cover art in ogg containers", func() {
|
||||||
@@ -116,7 +77,7 @@ Input #0, ogg, from '/Users/deluan/Music/iTunes/iTunes Media/Music/_Testes/Jamai
|
|||||||
metadata_block_picture: AAAAAwAAAAppbWFnZS9qcGVnAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4Id/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQ
|
metadata_block_picture: AAAAAwAAAAppbWFnZS9qcGVnAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4Id/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQ
|
||||||
TITLE : Jamaican In New York (Album Version)`
|
TITLE : Jamaican In New York (Album Version)`
|
||||||
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
||||||
Expect(md.HasPicture()).To(BeTrue())
|
Expect(md).To(HaveKey("has_picture"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("gets bitrate from the stream, if available", func() {
|
It("gets bitrate from the stream, if available", func() {
|
||||||
@@ -125,17 +86,7 @@ Input #0, mp3, from '/Users/deluan/Music/iTunes/iTunes Media/Music/Compilations/
|
|||||||
Duration: 00:00:01.02, start: 0.000000, bitrate: 477 kb/s
|
Duration: 00:00:01.02, start: 0.000000, bitrate: 477 kb/s
|
||||||
Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 192 kb/s`
|
Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 192 kb/s`
|
||||||
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
||||||
Expect(md.BitRate()).To(Equal(192))
|
Expect(md).To(HaveKeyWithValue("bitrate", []string{"192"}))
|
||||||
})
|
|
||||||
|
|
||||||
It("parses correctly the compilation tag", func() {
|
|
||||||
const output = `
|
|
||||||
Input #0, mp3, from '/Users/deluan/Music/iTunes/iTunes Media/Music/Compilations/Putumayo Presents Blues Lounge/09 Pablo's Blues.mp3':
|
|
||||||
Metadata:
|
|
||||||
compilation : 1
|
|
||||||
Duration: 00:05:02.63, start: 0.000000, bitrate: 140 kb/s`
|
|
||||||
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
|
||||||
Expect(md.Compilation()).To(BeTrue())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("parses duration with milliseconds", func() {
|
It("parses duration with milliseconds", func() {
|
||||||
@@ -143,7 +94,7 @@ Input #0, mp3, from '/Users/deluan/Music/iTunes/iTunes Media/Music/Compilations/
|
|||||||
Input #0, mp3, from '/Users/deluan/Music/iTunes/iTunes Media/Music/Compilations/Putumayo Presents Blues Lounge/09 Pablo's Blues.mp3':
|
Input #0, mp3, from '/Users/deluan/Music/iTunes/iTunes Media/Music/Compilations/Putumayo Presents Blues Lounge/09 Pablo's Blues.mp3':
|
||||||
Duration: 00:05:02.63, start: 0.000000, bitrate: 140 kb/s`
|
Duration: 00:05:02.63, start: 0.000000, bitrate: 140 kb/s`
|
||||||
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
||||||
Expect(md.Duration()).To(BeNumerically("~", 302.63, 0.001))
|
Expect(md).To(HaveKeyWithValue("duration", []string{"302.63"}))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("parses stream level tags", func() {
|
It("parses stream level tags", func() {
|
||||||
@@ -156,7 +107,7 @@ Input #0, ogg, from './01-02 Drive (Teku).opus':
|
|||||||
Metadata:
|
Metadata:
|
||||||
TITLE : Drive (Teku)`
|
TITLE : Drive (Teku)`
|
||||||
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
||||||
Expect(md.Title()).To(Equal("Drive (Teku)"))
|
Expect(md).To(HaveKeyWithValue("title", []string{"Drive (Teku)"}))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("does not overlap top level tags with the stream level tags", func() {
|
It("does not overlap top level tags with the stream level tags", func() {
|
||||||
@@ -168,33 +119,7 @@ Input #0, mp3, from 'groovin.mp3':
|
|||||||
Metadata:
|
Metadata:
|
||||||
title : garbage`
|
title : garbage`
|
||||||
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
||||||
Expect(md.Title()).To(Equal("Groovin' (feat. Daniel Sneijers, Susanne Alt)"))
|
Expect(md).To(HaveKeyWithValue("title", []string{"Groovin' (feat. Daniel Sneijers, Susanne Alt)", "garbage"}))
|
||||||
})
|
|
||||||
|
|
||||||
It("ignores case in the tag name", func() {
|
|
||||||
const output = `
|
|
||||||
Input #0, flac, from '/Users/deluan/Downloads/06. Back In Black.flac':
|
|
||||||
Metadata:
|
|
||||||
ALBUM : Back In Black
|
|
||||||
DATE : 1980.07.25
|
|
||||||
disc : 1
|
|
||||||
GENRE : Hard Rock
|
|
||||||
TITLE : Back In Black
|
|
||||||
DISCTOTAL : 1
|
|
||||||
TRACKTOTAL : 10
|
|
||||||
track : 6
|
|
||||||
Duration: 00:04:16.00, start: 0.000000, bitrate: 995 kb/s`
|
|
||||||
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
|
||||||
Expect(md.Title()).To(Equal("Back In Black"))
|
|
||||||
Expect(md.Album()).To(Equal("Back In Black"))
|
|
||||||
Expect(md.Genres()).To(ConsistOf("Hard Rock"))
|
|
||||||
n, t := md.TrackNumber()
|
|
||||||
Expect(n).To(Equal(6))
|
|
||||||
Expect(t).To(Equal(10))
|
|
||||||
n, t = md.DiscNumber()
|
|
||||||
Expect(n).To(Equal(1))
|
|
||||||
Expect(t).To(Equal(1))
|
|
||||||
Expect(md.Year()).To(Equal(1980))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("parses multiline tags", func() {
|
It("parses multiline tags", func() {
|
||||||
@@ -227,7 +152,7 @@ Tracklist:
|
|||||||
07. Wunderbar
|
07. Wunderbar
|
||||||
08. Quarta Dimensão`
|
08. Quarta Dimensão`
|
||||||
md, _ := e.extractMetadata("tests/fixtures/test.mp3", outputWithMultilineComment)
|
md, _ := e.extractMetadata("tests/fixtures/test.mp3", outputWithMultilineComment)
|
||||||
Expect(md.Comment()).To(Equal(expectedComment))
|
Expect(md).To(HaveKeyWithValue("comment", []string{expectedComment}))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("parses sort tags correctly", func() {
|
It("parses sort tags correctly", func() {
|
||||||
@@ -244,14 +169,14 @@ Input #0, mp3, from '/Users/deluan/Downloads/椎名林檎 - 加爾基 精液 栗
|
|||||||
ALBUMARTISTSORT : Shiina, Ringo
|
ALBUMARTISTSORT : Shiina, Ringo
|
||||||
`
|
`
|
||||||
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
||||||
Expect(md.Title()).To(Equal("ドツペルゲンガー"))
|
Expect(md).To(HaveKeyWithValue("title", []string{"ドツペルゲンガー"}))
|
||||||
Expect(md.Album()).To(Equal("加爾基 精液 栗ノ花"))
|
Expect(md).To(HaveKeyWithValue("album", []string{"加爾基 精液 栗ノ花"}))
|
||||||
Expect(md.Artist()).To(Equal("椎名林檎"))
|
Expect(md).To(HaveKeyWithValue("artist", []string{"椎名林檎"}))
|
||||||
Expect(md.AlbumArtist()).To(Equal("椎名林檎"))
|
Expect(md).To(HaveKeyWithValue("album_artist", []string{"椎名林檎"}))
|
||||||
Expect(md.SortTitle()).To(Equal("Dopperugengā"))
|
Expect(md).To(HaveKeyWithValue("title-sort", []string{"Dopperugengā"}))
|
||||||
Expect(md.SortAlbum()).To(Equal("Kalk Samen Kuri No Hana"))
|
Expect(md).To(HaveKeyWithValue("albumsort", []string{"Kalk Samen Kuri No Hana"}))
|
||||||
Expect(md.SortArtist()).To(Equal("Shiina, Ringo"))
|
Expect(md).To(HaveKeyWithValue("artist_sort", []string{"Shiina, Ringo"}))
|
||||||
Expect(md.SortAlbumArtist()).To(Equal("Shiina, Ringo"))
|
Expect(md).To(HaveKeyWithValue("albumartistsort", []string{"Shiina, Ringo"}))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("ignores cover comment", func() {
|
It("ignores cover comment", func() {
|
||||||
@@ -266,7 +191,7 @@ Input #0, mp3, from './Edie Brickell/Picture Perfect Morning/01-01 Tomorrow Come
|
|||||||
Metadata:
|
Metadata:
|
||||||
comment : Cover (front)`
|
comment : Cover (front)`
|
||||||
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
||||||
Expect(md.Comment()).To(Equal(""))
|
Expect(md).ToNot(HaveKey("comment"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("parses tags with spaces in the name", func() {
|
It("parses tags with spaces in the name", func() {
|
||||||
@@ -276,7 +201,7 @@ Input #0, mp3, from '/Users/deluan/Music/Music/Media/_/Wyclef Jean - From the Hu
|
|||||||
ALBUM ARTIST : Wyclef Jean
|
ALBUM ARTIST : Wyclef Jean
|
||||||
`
|
`
|
||||||
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
||||||
Expect(md.AlbumArtist()).To(Equal("Wyclef Jean"))
|
Expect(md).To(HaveKeyWithValue("album artist", []string{"Wyclef Jean"}))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -291,7 +216,7 @@ Input #0, mp3, from '/Users/deluan/Music/Music/Media/_/Wyclef Jean - From the Hu
|
|||||||
Metadata:
|
Metadata:
|
||||||
TBPM : 123`
|
TBPM : 123`
|
||||||
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
|
||||||
Expect(md.Bpm()).To(Equal(123))
|
Expect(md).To(HaveKeyWithValue("tbpm", []string{"123"}))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("parses and rounds a floating point fBPM tag", func() {
|
It("parses and rounds a floating point fBPM tag", func() {
|
||||||
@@ -300,6 +225,6 @@ Input #0, mp3, from '/Users/deluan/Music/Music/Media/_/Wyclef Jean - From the Hu
|
|||||||
Metadata:
|
Metadata:
|
||||||
FBPM : 141.7`
|
FBPM : 141.7`
|
||||||
md, _ := e.extractMetadata("tests/fixtures/test.ogg", output)
|
md, _ := e.extractMetadata("tests/fixtures/test.ogg", output)
|
||||||
Expect(md.Bpm()).To(Equal(142))
|
Expect(md).To(HaveKeyWithValue("fbpm", []string{"141.7"}))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -10,53 +10,59 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/navidrome/navidrome/scanner/metadata/ffmpeg"
|
||||||
|
|
||||||
|
"github.com/navidrome/navidrome/scanner/metadata/taglib"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/navidrome/navidrome/conf"
|
"github.com/navidrome/navidrome/conf"
|
||||||
"github.com/navidrome/navidrome/log"
|
"github.com/navidrome/navidrome/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Extractor interface {
|
type Parser interface {
|
||||||
Extract(files ...string) (map[string]*Tags, error)
|
Parse(files ...string) (map[string]map[string][]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Extract(files ...string) (map[string]*Tags, error) {
|
func Extract(files ...string) (map[string]*Tags, error) {
|
||||||
var e Extractor
|
var e Parser
|
||||||
|
|
||||||
switch conf.Server.Scanner.Extractor {
|
switch conf.Server.Scanner.Extractor {
|
||||||
case "taglib":
|
case "taglib":
|
||||||
e = &taglibExtractor{}
|
e = &taglib.Parser{}
|
||||||
case "ffmpeg":
|
case "ffmpeg":
|
||||||
e = &ffmpegExtractor{}
|
e = &ffmpeg.Parser{}
|
||||||
default:
|
default:
|
||||||
log.Warn("Invalid Scanner.Extractor option. Using default taglib", "requested", conf.Server.Scanner.Extractor,
|
log.Warn("Invalid 'Scanner.Extractor' option. Using default 'taglib'", "requested", conf.Server.Scanner.Extractor,
|
||||||
"validOptions", "ffmpeg,taglib")
|
"validOptions", "ffmpeg,taglib")
|
||||||
e = &taglibExtractor{}
|
e = &taglib.Parser{}
|
||||||
}
|
}
|
||||||
return e.Extract(files...)
|
extractedTags, err := e.Parse(files...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := map[string]*Tags{}
|
||||||
|
for filePath, tags := range extractedTags {
|
||||||
|
fileInfo, err := os.Stat(filePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Error stating file. Skipping", "filePath", filePath, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result[filePath] = &Tags{
|
||||||
|
filePath: filePath,
|
||||||
|
fileInfo: fileInfo,
|
||||||
|
tags: tags,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tags struct {
|
type Tags struct {
|
||||||
filePath string
|
filePath string
|
||||||
suffix string
|
|
||||||
fileInfo os.FileInfo
|
fileInfo os.FileInfo
|
||||||
tags map[string][]string
|
tags map[string][]string
|
||||||
custom map[string][]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTags(filePath string, tags, custom map[string][]string) *Tags {
|
|
||||||
fileInfo, err := os.Stat(filePath)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Error stating file. Skipping", "filePath", filePath, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Tags{
|
|
||||||
filePath: filePath,
|
|
||||||
suffix: strings.ToLower(strings.TrimPrefix(path.Ext(filePath), ".")),
|
|
||||||
fileInfo: fileInfo,
|
|
||||||
tags: tags,
|
|
||||||
custom: custom,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common tags
|
// Common tags
|
||||||
@@ -109,11 +115,10 @@ func (t *Tags) BitRate() int { return t.getInt("bitrate") }
|
|||||||
func (t *Tags) ModificationTime() time.Time { return t.fileInfo.ModTime() }
|
func (t *Tags) ModificationTime() time.Time { return t.fileInfo.ModTime() }
|
||||||
func (t *Tags) Size() int64 { return t.fileInfo.Size() }
|
func (t *Tags) Size() int64 { return t.fileInfo.Size() }
|
||||||
func (t *Tags) FilePath() string { return t.filePath }
|
func (t *Tags) FilePath() string { return t.filePath }
|
||||||
func (t *Tags) Suffix() string { return t.suffix }
|
func (t *Tags) Suffix() string { return strings.ToLower(strings.TrimPrefix(path.Ext(t.filePath), ".")) }
|
||||||
|
|
||||||
func (t *Tags) getTags(tagNames ...string) []string {
|
func (t *Tags) getTags(tagNames ...string) []string {
|
||||||
allTags := append(tagNames, t.custom[tagNames[0]]...)
|
for _, tag := range tagNames {
|
||||||
for _, tag := range allTags {
|
|
||||||
if v, ok := t.tags[tag]; ok {
|
if v, ok := t.tags[tag]; ok {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
@@ -130,7 +135,6 @@ func (t *Tags) getFirstTagValue(tagNames ...string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tags) getAllTagValues(tagNames ...string) []string {
|
func (t *Tags) getAllTagValues(tagNames ...string) []string {
|
||||||
tagNames = append(tagNames, t.custom[tagNames[0]]...)
|
|
||||||
var values []string
|
var values []string
|
||||||
for _, tag := range tagNames {
|
for _, tag := range tagNames {
|
||||||
if v, ok := t.tags[tag]; ok {
|
if v, ok := t.tags[tag]; ok {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestScanner(t *testing.T) {
|
func TestMetadata(t *testing.T) {
|
||||||
tests.Init(t, true)
|
tests.Init(t, true)
|
||||||
log.SetLevel(log.LevelCritical)
|
log.SetLevel(log.LevelCritical)
|
||||||
RegisterFailHandler(Fail)
|
RegisterFailHandler(Fail)
|
||||||
|
|||||||
@@ -1,11 +1,55 @@
|
|||||||
package metadata
|
package metadata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/navidrome/navidrome/conf"
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Tags", func() {
|
var _ = Describe("Tags", func() {
|
||||||
|
Context("Extract", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
conf.Server.Scanner.Extractor = "taglib"
|
||||||
|
})
|
||||||
|
|
||||||
|
It("correctly parses metadata from all files in folder", func() {
|
||||||
|
mds, err := Extract("tests/fixtures/test.mp3", "tests/fixtures/test.ogg")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(mds).To(HaveLen(2))
|
||||||
|
|
||||||
|
m := mds["tests/fixtures/test.mp3"]
|
||||||
|
Expect(m.Title()).To(Equal("Song"))
|
||||||
|
Expect(m.Album()).To(Equal("Album"))
|
||||||
|
Expect(m.Artist()).To(Equal("Artist"))
|
||||||
|
Expect(m.AlbumArtist()).To(Equal("Album Artist"))
|
||||||
|
Expect(m.Compilation()).To(BeTrue())
|
||||||
|
Expect(m.Genres()).To(Equal([]string{"Rock"}))
|
||||||
|
Expect(m.Year()).To(Equal(2014))
|
||||||
|
n, t := m.TrackNumber()
|
||||||
|
Expect(n).To(Equal(2))
|
||||||
|
Expect(t).To(Equal(10))
|
||||||
|
n, t = m.DiscNumber()
|
||||||
|
Expect(n).To(Equal(1))
|
||||||
|
Expect(t).To(Equal(2))
|
||||||
|
Expect(m.HasPicture()).To(BeTrue())
|
||||||
|
Expect(m.Duration()).To(BeNumerically("~", 1, 0.01))
|
||||||
|
Expect(m.BitRate()).To(Equal(192))
|
||||||
|
Expect(m.FilePath()).To(Equal("tests/fixtures/test.mp3"))
|
||||||
|
Expect(m.Suffix()).To(Equal("mp3"))
|
||||||
|
Expect(m.Size()).To(Equal(int64(51876)))
|
||||||
|
|
||||||
|
m = mds["tests/fixtures/test.ogg"]
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(m.Title()).To(BeEmpty())
|
||||||
|
Expect(m.HasPicture()).To(BeFalse())
|
||||||
|
Expect(m.Duration()).To(BeNumerically("~", 1.00, 0.01))
|
||||||
|
Expect(m.BitRate()).To(Equal(18))
|
||||||
|
Expect(m.Suffix()).To(Equal("ogg"))
|
||||||
|
Expect(m.FilePath()).To(Equal("tests/fixtures/test.ogg"))
|
||||||
|
Expect(m.Size()).To(Equal(int64(5065)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
Describe("getYear", func() {
|
Describe("getYear", func() {
|
||||||
It("parses the year correctly", func() {
|
It("parses the year correctly", func() {
|
||||||
var examples = map[string]int{
|
var examples = map[string]int{
|
||||||
@@ -65,12 +109,23 @@ var _ = Describe("Tags", func() {
|
|||||||
It("returns values from all tag names", func() {
|
It("returns values from all tag names", func() {
|
||||||
md := &Tags{}
|
md := &Tags{}
|
||||||
md.tags = map[string][]string{
|
md.tags = map[string][]string{
|
||||||
"genre": {"Rock", "Pop"},
|
"genre": {"Rock", "Pop", "New Wave"},
|
||||||
"_genre": {"New Wave"},
|
|
||||||
}
|
}
|
||||||
md.custom = map[string][]string{"genre": {"_genre"}}
|
|
||||||
|
|
||||||
Expect(md.Genres()).To(ConsistOf("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))
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
package metadata
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/navidrome/navidrome/log"
|
|
||||||
"github.com/navidrome/navidrome/scanner/metadata/taglib"
|
|
||||||
)
|
|
||||||
|
|
||||||
type taglibExtractor struct{}
|
|
||||||
|
|
||||||
func (e *taglibExtractor) Extract(paths ...string) (map[string]*Tags, error) {
|
|
||||||
fileTags := map[string]*Tags{}
|
|
||||||
for _, path := range paths {
|
|
||||||
tags, err := e.extractMetadata(path)
|
|
||||||
if err == nil {
|
|
||||||
fileTags[path] = tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fileTags, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *taglibExtractor) extractMetadata(filePath string) (*Tags, error) {
|
|
||||||
parsedTags, err := taglib.Read(filePath)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Error reading metadata from file. Skipping", "filePath", filePath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tags := NewTags(filePath, parsedTags, map[string][]string{
|
|
||||||
"title": {"_track", "titlesort"},
|
|
||||||
"album": {"_album", "albumsort"},
|
|
||||||
"artist": {"_artist", "artistsort"},
|
|
||||||
"date": {"_year"},
|
|
||||||
"track": {"_track"},
|
|
||||||
})
|
|
||||||
|
|
||||||
return tags, nil
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package taglib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/navidrome/navidrome/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Parser struct{}
|
||||||
|
|
||||||
|
type parsedTags = map[string][]string
|
||||||
|
|
||||||
|
func (e *Parser) Parse(paths ...string) (map[string]parsedTags, error) {
|
||||||
|
fileTags := map[string]parsedTags{}
|
||||||
|
for _, path := range paths {
|
||||||
|
tags, err := e.extractMetadata(path)
|
||||||
|
if err == nil {
|
||||||
|
fileTags[path] = tags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fileTags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Parser) extractMetadata(filePath string) (parsedTags, error) {
|
||||||
|
tags, err := Read(filePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Error reading metadata from file. Skipping", "filePath", filePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
alternativeTags := map[string][]string{
|
||||||
|
"title": {"titlesort"},
|
||||||
|
"album": {"albumsort"},
|
||||||
|
"artist": {"artistsort"},
|
||||||
|
"tracknumber": {"trck", "_track"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for tagName, alternatives := range alternativeTags {
|
||||||
|
for _, altName := range alternatives {
|
||||||
|
if altValue, ok := tags[altName]; ok {
|
||||||
|
tags[tagName] = append(tags[tagName], altValue...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tags, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package taglib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/navidrome/navidrome/log"
|
||||||
|
"github.com/navidrome/navidrome/tests"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTagLib(t *testing.T) {
|
||||||
|
tests.Init(t, true)
|
||||||
|
log.SetLevel(log.LevelCritical)
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "TagLib Suite")
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package taglib
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Parser", func() {
|
||||||
|
var e *Parser
|
||||||
|
BeforeEach(func() {
|
||||||
|
e = &Parser{}
|
||||||
|
})
|
||||||
|
Context("Parse", func() {
|
||||||
|
It("correctly parses metadata from all files in folder", func() {
|
||||||
|
mds, err := e.Parse("tests/fixtures/test.mp3", "tests/fixtures/test.ogg")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(mds).To(HaveLen(2))
|
||||||
|
|
||||||
|
m := mds["tests/fixtures/test.mp3"]
|
||||||
|
Expect(m).To(HaveKeyWithValue("title", []string{"Song", "Song"}))
|
||||||
|
Expect(m).To(HaveKeyWithValue("album", []string{"Album", "Album"}))
|
||||||
|
Expect(m).To(HaveKeyWithValue("artist", []string{"Artist", "Artist"}))
|
||||||
|
Expect(m).To(HaveKeyWithValue("albumartist", []string{"Album Artist"}))
|
||||||
|
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"}))
|
||||||
|
Expect(m).To(HaveKeyWithValue("bitrate", []string{"192"}))
|
||||||
|
Expect(m).To(HaveKeyWithValue("comment", []string{"Comment1\nComment2"}))
|
||||||
|
Expect(m).To(HaveKeyWithValue("lyrics", []string{"Lyrics 1\rLyrics 2"}))
|
||||||
|
Expect(m).To(HaveKeyWithValue("bpm", []string{"123"}))
|
||||||
|
|
||||||
|
m = mds["tests/fixtures/test.ogg"]
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(m).ToNot(HaveKey("title"))
|
||||||
|
Expect(m).ToNot(HaveKey("has_picture"))
|
||||||
|
Expect(m).To(HaveKeyWithValue("duration", []string{"1"}))
|
||||||
|
Expect(m).To(HaveKeyWithValue("fbpm", []string{"141.7"}))
|
||||||
|
|
||||||
|
// TabLib 1.12 returns 18, previous versions return 39.
|
||||||
|
// See https://github.com/taglib/taglib/commit/2f238921824741b2cfe6fbfbfc9701d9827ab06b
|
||||||
|
Expect(m).To(HaveKey("bitrate"))
|
||||||
|
Expect(m["bitrate"][0]).To(BeElementOf("18", "39"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
+5
-5
@@ -13,7 +13,7 @@
|
|||||||
#include <tpropertymap.h>
|
#include <tpropertymap.h>
|
||||||
#include <vorbisfile.h>
|
#include <vorbisfile.h>
|
||||||
|
|
||||||
#include "taglib_parser.h"
|
#include "taglib_wrapper.h"
|
||||||
|
|
||||||
char has_cover(const TagLib::FileRef f);
|
char has_cover(const TagLib::FileRef f);
|
||||||
|
|
||||||
@@ -39,16 +39,16 @@ int taglib_read(const char *filename, unsigned long id) {
|
|||||||
TagLib::Tag *basic = f.file()->tag();
|
TagLib::Tag *basic = f.file()->tag();
|
||||||
if (!basic->isEmpty()) {
|
if (!basic->isEmpty()) {
|
||||||
if (!basic->title().isEmpty()) {
|
if (!basic->title().isEmpty()) {
|
||||||
tags.insert("_title", basic->title());
|
tags.insert("title", basic->title());
|
||||||
}
|
}
|
||||||
if (!basic->artist().isEmpty()) {
|
if (!basic->artist().isEmpty()) {
|
||||||
tags.insert("_artist", basic->artist());
|
tags.insert("artist", basic->artist());
|
||||||
}
|
}
|
||||||
if (!basic->album().isEmpty()) {
|
if (!basic->album().isEmpty()) {
|
||||||
tags.insert("_album", basic->album());
|
tags.insert("album", basic->album());
|
||||||
}
|
}
|
||||||
if (basic->year() > 0) {
|
if (basic->year() > 0) {
|
||||||
tags.insert("_year", TagLib::String::number(basic->year()));
|
tags.insert("date", TagLib::String::number(basic->year()));
|
||||||
}
|
}
|
||||||
if (basic->track() > 0) {
|
if (basic->track() > 0) {
|
||||||
tags.insert("_track", TagLib::String::number(basic->track()));
|
tags.insert("_track", TagLib::String::number(basic->track()));
|
||||||
+1
-1
@@ -7,7 +7,7 @@ package taglib
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "taglib_parser.h"
|
#include "taglib_wrapper.h"
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package metadata
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = Describe("taglibExtractor", func() {
|
|
||||||
Context("Extract", func() {
|
|
||||||
It("correctly parses metadata from all files in folder", func() {
|
|
||||||
e := &taglibExtractor{}
|
|
||||||
mds, err := e.Extract("tests/fixtures/test.mp3", "tests/fixtures/test.ogg")
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
Expect(mds).To(HaveLen(2))
|
|
||||||
|
|
||||||
m := mds["tests/fixtures/test.mp3"]
|
|
||||||
Expect(m.Title()).To(Equal("Song"))
|
|
||||||
Expect(m.Album()).To(Equal("Album"))
|
|
||||||
Expect(m.Artist()).To(Equal("Artist"))
|
|
||||||
Expect(m.AlbumArtist()).To(Equal("Album Artist"))
|
|
||||||
Expect(m.Compilation()).To(BeTrue())
|
|
||||||
Expect(m.Genres()).To(ConsistOf("Rock"))
|
|
||||||
Expect(m.Year()).To(Equal(2014))
|
|
||||||
n, t := m.TrackNumber()
|
|
||||||
Expect(n).To(Equal(2))
|
|
||||||
Expect(t).To(Equal(10))
|
|
||||||
n, t = m.DiscNumber()
|
|
||||||
Expect(n).To(Equal(1))
|
|
||||||
Expect(t).To(Equal(2))
|
|
||||||
Expect(m.HasPicture()).To(BeTrue())
|
|
||||||
Expect(m.Duration()).To(Equal(float32(1)))
|
|
||||||
Expect(m.BitRate()).To(Equal(192))
|
|
||||||
Expect(m.FilePath()).To(Equal("tests/fixtures/test.mp3"))
|
|
||||||
Expect(m.Suffix()).To(Equal("mp3"))
|
|
||||||
Expect(m.Size()).To(Equal(int64(51876)))
|
|
||||||
Expect(m.Comment()).To(Equal("Comment1\nComment2"))
|
|
||||||
Expect(m.Bpm()).To(Equal(123))
|
|
||||||
|
|
||||||
m = mds["tests/fixtures/test.ogg"]
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
Expect(m.Title()).To(BeEmpty())
|
|
||||||
Expect(m.HasPicture()).To(BeFalse())
|
|
||||||
Expect(m.Duration()).To(Equal(float32(1)))
|
|
||||||
Expect(m.Suffix()).To(Equal("ogg"))
|
|
||||||
Expect(m.FilePath()).To(Equal("tests/fixtures/test.ogg"))
|
|
||||||
Expect(m.Size()).To(Equal(int64(5065)))
|
|
||||||
Expect(m.Bpm()).To(Equal(142)) // This file has a floating point BPM set to 141.7 under the fBPM tag. Ensure we parse and round correctly.
|
|
||||||
|
|
||||||
// TabLib 1.12 returns 18, previous versions return 39.
|
|
||||||
// See https://github.com/taglib/taglib/commit/2f238921824741b2cfe6fbfbfc9701d9827ab06b
|
|
||||||
Expect(m.BitRate()).To(BeElementOf(18, 39))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Reference in New Issue
Block a user