From 472324e280b5e2e4f9911e37a6ffacd3a753a03d Mon Sep 17 00:00:00 2001 From: Deluan Date: Sat, 11 May 2024 16:54:59 -0400 Subject: [PATCH] Read sampleRate from audio files --- scanner/metadata/ffmpeg/ffmpeg.go | 4 +++- scanner/metadata/ffmpeg/ffmpeg_test.go | 18 ++++++++++++++++ scanner/metadata/taglib/taglib_test.go | 25 ++++++++++++---------- scanner/metadata/taglib/taglib_wrapper.cpp | 1 + 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/scanner/metadata/ffmpeg/ffmpeg.go b/scanner/metadata/ffmpeg/ffmpeg.go index 04e7f690..1d68e716 100644 --- a/scanner/metadata/ffmpeg/ffmpeg.go +++ b/scanner/metadata/ffmpeg/ffmpeg.go @@ -81,7 +81,8 @@ var ( // Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 192 kb/s // Stream #0:0: Audio: flac, 44100 Hz, stereo, s16 - audioStreamRx = regexp.MustCompile(`^\s{2,4}Stream #\d+:\d+.*: Audio: (.*), (.* Hz), ([\w.]+),*(.*.,)*`) + // Stream #0:0: Audio: dsd_lsbf_planar, 352800 Hz, stereo, fltp, 5644 kb/s + audioStreamRx = regexp.MustCompile(`^\s{2,4}Stream #\d+:\d+.*: Audio: (.*), (.*) Hz, ([\w.]+),*(.*.,)*`) // Stream #0:1: Video: mjpeg, yuvj444p(pc, bt470bg/unknown/unknown), 600x600 [SAR 1:1 DAR 1:1], 90k tbr, 90k tbn, 90k tbc` coverRx = regexp.MustCompile(`^\s{2,4}Stream #\d+:.+: (Video):.*`) @@ -166,6 +167,7 @@ func (e *Extractor) parseInfo(info string) map[string][]string { match = audioStreamRx.FindStringSubmatch(line) if len(match) > 0 { + tags["samplerate"] = []string{match[2]} tags["channels"] = []string{e.parseChannels(match[3])} } } diff --git a/scanner/metadata/ffmpeg/ffmpeg_test.go b/scanner/metadata/ffmpeg/ffmpeg_test.go index 8f8409d1..6c7f43a5 100644 --- a/scanner/metadata/ffmpeg/ffmpeg_test.go +++ b/scanner/metadata/ffmpeg/ffmpeg_test.go @@ -174,6 +174,24 @@ Input #0, flac, from '/Users/deluan/Music/iTunes/iTunes Media/Music/Compilations Expect(md).To(HaveKeyWithValue("channels", []string{"2"})) }) + It("parse sampleRate from the stream", func() { + const output = ` +Input #0, dsf, from '/Users/deluan/Downloads/06-04 Perpetual Change.dsf': + Duration: 00:14:19.46, start: 0.000000, bitrate: 5644 kb/s + Stream #0:0: Audio: dsd_lsbf_planar, 352800 Hz, stereo, fltp, 5644 kb/s` + md, _ := e.extractMetadata("tests/fixtures/test.mp3", output) + Expect(md).To(HaveKeyWithValue("samplerate", []string{"352800"})) + }) + + It("parse sampleRate from the stream", func() { + const output = ` +Input #0, wav, from '/Users/deluan/Music/Music/Media/_/multichannel/Nums_7dot1_24_48000.wav': + Duration: 00:00:09.05, bitrate: 9216 kb/s + Stream #0:0: Audio: pcm_s24le ([1][0][0][0] / 0x0001), 48000 Hz, 7.1, s32 (24 bit), 9216 kb/s` + md, _ := e.extractMetadata("tests/fixtures/test.mp3", output) + Expect(md).To(HaveKeyWithValue("samplerate", []string{"48000"})) + }) + It("parses stream level tags", func() { const output = ` Input #0, ogg, from './01-02 Drive (Teku).opus': diff --git a/scanner/metadata/taglib/taglib_test.go b/scanner/metadata/taglib/taglib_test.go index 31007910..69ab2575 100644 --- a/scanner/metadata/taglib/taglib_test.go +++ b/scanner/metadata/taglib/taglib_test.go @@ -26,6 +26,7 @@ var _ = Describe("Extractor", func() { Expect(err).NotTo(HaveOccurred()) Expect(mds).To(HaveLen(2)) + // Test MP3 m := mds["tests/fixtures/test.mp3"] Expect(m).To(HaveKeyWithValue("title", []string{"Song", "Song"})) Expect(m).To(HaveKeyWithValue("album", []string{"Album", "Album"})) @@ -44,6 +45,7 @@ var _ = Describe("Extractor", func() { Expect(m).To(HaveKeyWithValue("duration", []string{"1.02"})) Expect(m).To(HaveKeyWithValue("bitrate", []string{"192"})) Expect(m).To(HaveKeyWithValue("channels", []string{"2"})) + Expect(m).To(HaveKeyWithValue("samplerate", []string{"44100"})) Expect(m).To(HaveKeyWithValue("comment", []string{"Comment1\nComment2"})) Expect(m).ToNot(HaveKey("lyrics")) Expect(m).To(Or(HaveKeyWithValue("lyrics-eng", []string{ @@ -70,11 +72,13 @@ var _ = Describe("Extractor", func() { m = m.Map(e.CustomMappings()) Expect(m).To(HaveKeyWithValue("tracknumber", []string{"2/10", "2/10", "2"})) + // Test OGG m = mds["tests/fixtures/test.ogg"] Expect(err).To(BeNil()) Expect(m).ToNot(HaveKey("has_picture")) Expect(m).To(HaveKeyWithValue("duration", []string{"1.04"})) Expect(m).To(HaveKeyWithValue("fbpm", []string{"141.7"})) + Expect(m).To(HaveKeyWithValue("samplerate", []string{"8000"})) // TabLib 1.12 returns 18, previous versions return 39. // See https://github.com/taglib/taglib/commit/2f238921824741b2cfe6fbfbfc9701d9827ab06b @@ -83,7 +87,7 @@ var _ = Describe("Extractor", func() { }) DescribeTable("Format-Specific tests", - func(file, duration, channels, albumGain, albumPeak, trackGain, trackPeak string, id3Lyrics bool) { + func(file, duration, channels, samplerate, albumGain, albumPeak, trackGain, trackPeak string, id3Lyrics bool) { file = "tests/fixtures/" + file mds, err := e.Parse(file) Expect(err).NotTo(HaveOccurred()) @@ -122,6 +126,7 @@ var _ = Describe("Extractor", func() { Expect(m).To(HaveKeyWithValue("duration", []string{duration})) Expect(m).To(HaveKeyWithValue("channels", []string{channels})) + Expect(m).To(HaveKeyWithValue("samplerate", []string{samplerate})) Expect(m).To(HaveKeyWithValue("comment", []string{"Comment1\nComment2"})) if id3Lyrics { @@ -147,26 +152,24 @@ var _ = Describe("Extractor", func() { }, // ffmpeg -f lavfi -i "sine=frequency=1200:duration=1" test.flac - Entry("correctly parses flac tags", "test.flac", "1.00", "1", "+4.06 dB", "0.12496948", "+4.06 dB", "0.12496948", false), + Entry("correctly parses flac tags", "test.flac", "1.00", "1", "44100", "+4.06 dB", "0.12496948", "+4.06 dB", "0.12496948", false), - Entry("Correctly parses m4a (aac) gain tags", "01 Invisible (RED) Edit Version.m4a", "1.04", "2", "0.37", "0.48", "0.37", "0.48", false), - Entry("Correctly parses m4a (aac) gain tags (uppercase)", "test.m4a", "1.04", "2", "0.37", "0.48", "0.37", "0.48", false), - - Entry("correctly parses ogg (vorbis) tags", "test.ogg", "1.04", "2", "+7.64 dB", "0.11772506", "+7.64 dB", "0.11772506", false), + Entry("Correctly parses m4a (aac) gain tags", "01 Invisible (RED) Edit Version.m4a", "1.04", "2", "44100", "0.37", "0.48", "0.37", "0.48", false), + Entry("Correctly parses m4a (aac) gain tags (uppercase)", "test.m4a", "1.04", "2", "44100", "0.37", "0.48", "0.37", "0.48", false), + Entry("correctly parses ogg (vorbis) tags", "test.ogg", "1.04", "2", "8000", "+7.64 dB", "0.11772506", "+7.64 dB", "0.11772506", false), // ffmpeg -f lavfi -i "sine=frequency=900:duration=1" test.wma // Weird note: for the tag parsing to work, the lyrics are actually stored in the reverse order - Entry("correctly parses wma/asf tags", "test.wma", "1.02", "1", "3.27 dB", "0.132914", "3.27 dB", "0.132914", false), + Entry("correctly parses wma/asf tags", "test.wma", "1.02", "1", "44100", "3.27 dB", "0.132914", "3.27 dB", "0.132914", false), // ffmpeg -f lavfi -i "sine=frequency=800:duration=1" test.wv - Entry("correctly parses wv (wavpak) tags", "test.wv", "1.00", "1", "3.43 dB", "0.125061", "3.43 dB", "0.125061", false), + Entry("correctly parses wv (wavpak) tags", "test.wv", "1.00", "1", "44100", "3.43 dB", "0.125061", "3.43 dB", "0.125061", false), - // TODO - these break in the pipeline as it uses TabLib 1.11. Once Ubuntu 24.04 is released we can uncomment these tests // ffmpeg -f lavfi -i "sine=frequency=1000:duration=1" test.wav - // Entry("correctly parses wav tags", "test.wav", "1.00", "1", "3.06 dB", "0.125056", "3.06 dB", "0.125056", true), + Entry("correctly parses wav tags", "test.wav", "1.00", "1", "44100", "3.06 dB", "0.125056", "3.06 dB", "0.125056", true), // ffmpeg -f lavfi -i "sine=frequency=1400:duration=1" test.aiff - // Entry("correctly parses aiff tags", "test.aiff", "1.00", "1", "2.00 dB", "0.124972", "2.00 dB", "0.124972", true), + Entry("correctly parses aiff tags", "test.aiff", "1.00", "1", "44100", "2.00 dB", "0.124972", "2.00 dB", "0.124972", true), ) // Skip these tests when running as root diff --git a/scanner/metadata/taglib/taglib_wrapper.cpp b/scanner/metadata/taglib/taglib_wrapper.cpp index a3c46e65..b5bc59e2 100644 --- a/scanner/metadata/taglib/taglib_wrapper.cpp +++ b/scanner/metadata/taglib/taglib_wrapper.cpp @@ -45,6 +45,7 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) { go_map_put_int(id, (char *)"lengthinmilliseconds", props->lengthInMilliseconds()); go_map_put_int(id, (char *)"bitrate", props->bitrate()); go_map_put_int(id, (char *)"channels", props->channels()); + go_map_put_int(id, (char *)"samplerate", props->sampleRate()); // Create a map to collect all the tags TagLib::PropertyMap tags = f.file()->properties();