From a19a643c656f2a9e312e278b777d29f5d0495896 Mon Sep 17 00:00:00 2001 From: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Date: Wed, 17 May 2023 20:00:16 +0000 Subject: [PATCH] Manually add replaygain tags for m4a (#2346) * manually add replaygain tags for m4a * Add replaygain tests for m4a, mp4, ogg * add new valye for bitrate --- scanner/metadata/metadata_test.go | 6 ++-- scanner/metadata/taglib/taglib_test.go | 30 +++++++++++++++++- scanner/metadata/taglib/taglib_wrapper.cpp | 30 ++++++++++++++++++ scanner/tag_scanner_test.go | 3 +- scanner/walk_dir_tree_test.go | 2 +- .../01 Invisible (RED) Edit Version.m4a | Bin 0 -> 17011 bytes tests/fixtures/test.mp3 | Bin 51876 -> 52050 bytes tests/fixtures/test.ogg | Bin 5065 -> 5178 bytes 8 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 tests/fixtures/01 Invisible (RED) Edit Version.m4a diff --git a/scanner/metadata/metadata_test.go b/scanner/metadata/metadata_test.go index 07ad83957..98d2d8306 100644 --- a/scanner/metadata/metadata_test.go +++ b/scanner/metadata/metadata_test.go @@ -40,7 +40,7 @@ var _ = Describe("Tags", func() { Expect(m.Channels()).To(Equal(2)) Expect(m.FilePath()).To(Equal("tests/fixtures/test.mp3")) Expect(m.Suffix()).To(Equal("mp3")) - Expect(m.Size()).To(Equal(int64(51876))) + Expect(m.Size()).To(Equal(int64(52050))) Expect(m.RGAlbumGain()).To(Equal(3.21518)) Expect(m.RGAlbumPeak()).To(Equal(0.9125)) Expect(m.RGTrackGain()).To(Equal(-1.48)) @@ -53,10 +53,10 @@ var _ = Describe("Tags", func() { Expect(m.Duration()).To(BeNumerically("~", 1.04, 0.01)) Expect(m.Suffix()).To(Equal("ogg")) Expect(m.FilePath()).To(Equal("tests/fixtures/test.ogg")) - Expect(m.Size()).To(Equal(int64(5065))) + Expect(m.Size()).To(Equal(int64(5178))) // TabLib 1.12 returns 18, previous versions return 39. // See https://github.com/taglib/taglib/commit/2f238921824741b2cfe6fbfbfc9701d9827ab06b - Expect(m.BitRate()).To(BeElementOf(18, 39)) + Expect(m.BitRate()).To(BeElementOf(18, 39, 40)) }) }) }) diff --git a/scanner/metadata/taglib/taglib_test.go b/scanner/metadata/taglib/taglib_test.go index 14ff223e3..6bf7d3b51 100644 --- a/scanner/metadata/taglib/taglib_test.go +++ b/scanner/metadata/taglib/taglib_test.go @@ -65,7 +65,35 @@ var _ = Describe("Extractor", func() { // 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")) + Expect(m["bitrate"][0]).To(BeElementOf("18", "39", "40")) + }) + + Context("ReplayGain", func() { + testGain := func(file, albumGain, albumPeak, trackGain, trackPeak string) { + file = "tests/fixtures/" + file + mds, err := e.Parse(file) + Expect(err).NotTo(HaveOccurred()) + Expect(mds).To(HaveLen(1)) + + m := mds[file] + + Expect(m).To(HaveKeyWithValue("replaygain_album_gain", []string{albumGain})) + Expect(m).To(HaveKeyWithValue("replaygain_album_peak", []string{albumPeak})) + Expect(m).To(HaveKeyWithValue("replaygain_track_gain", []string{trackGain})) + Expect(m).To(HaveKeyWithValue("replaygain_track_peak", []string{trackPeak})) + } + + It("Correctly parses m4a (aac) gain tags", func() { + testGain("01 Invisible (RED) Edit Version.m4a", "0.37", "0.48", "0.37", "0.48") + }) + + It("correctly parses mp3 tags", func() { + testGain("test.mp3", "+3.21518 dB", "0.9125", "-1.48 dB", "0.4512") + }) + + It("correctly parses ogg (vorbis) tags", func() { + testGain("test.ogg", "+7.64 dB", "0.11772506", "+7.64 dB", "0.11772506") + }) }) }) diff --git a/scanner/metadata/taglib/taglib_wrapper.cpp b/scanner/metadata/taglib/taglib_wrapper.cpp index ea7f72eab..3f111c137 100644 --- a/scanner/metadata/taglib/taglib_wrapper.cpp +++ b/scanner/metadata/taglib/taglib_wrapper.cpp @@ -15,6 +15,13 @@ #include "taglib_wrapper.h" +// Tags necessary for M4a parsing +const char *RG_TAGS[] = { + "replaygain_album_gain", + "replaygain_album_peak", + "replaygain_track_gain", + "replaygain_track_peak"}; + char has_cover(const TagLib::FileRef f); int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) { @@ -70,6 +77,29 @@ int taglib_read(const FILENAME_CHAR_T *filename, unsigned long id) { } } + TagLib::MP4::File *m4afile(dynamic_cast(f.file())); + if (m4afile != NULL) + { + const auto itemListMap = m4afile->tag(); + { + char buf[200]; + + for (const char *key : RG_TAGS) + { + snprintf(buf, sizeof(buf), "----:com.apple.iTunes:%s", key); + const auto item = itemListMap->item(buf); + if (item.isValid()) + { + char *dup = ::strdup(key); + char *val = ::strdup(item.toStringList().front().toCString(true)); + go_map_put_str(id, dup, val); + free(dup); + free(val); + } + } + } + } + if (has_cover(f)) { go_map_put_str(id, (char *)"has_picture", (char *)"true"); } diff --git a/scanner/tag_scanner_test.go b/scanner/tag_scanner_test.go index e5bb7be5c..215e06bc3 100644 --- a/scanner/tag_scanner_test.go +++ b/scanner/tag_scanner_test.go @@ -10,11 +10,12 @@ var _ = Describe("TagScanner", func() { It("return all audio files from the folder", func() { files, err := loadAllAudioFiles("tests/fixtures") Expect(err).ToNot(HaveOccurred()) - Expect(files).To(HaveLen(4)) + Expect(files).To(HaveLen(5)) Expect(files).To(HaveKey("tests/fixtures/test.ogg")) Expect(files).To(HaveKey("tests/fixtures/test.mp3")) Expect(files).To(HaveKey("tests/fixtures/test_no_read_permission.ogg")) Expect(files).To(HaveKey("tests/fixtures/01 Invisible (RED) Edit Version.mp3")) + Expect(files).To(HaveKey("tests/fixtures/01 Invisible (RED) Edit Version.m4a")) Expect(files).ToNot(HaveKey("tests/fixtures/._02 Invisible.mp3")) Expect(files).ToNot(HaveKey("tests/fixtures/playlist.m3u")) }) diff --git a/scanner/walk_dir_tree_test.go b/scanner/walk_dir_tree_test.go index 7ef159f02..42277adfa 100644 --- a/scanner/walk_dir_tree_test.go +++ b/scanner/walk_dir_tree_test.go @@ -36,7 +36,7 @@ var _ = Describe("walk_dir_tree", func() { Expect(collected[baseDir]).To(MatchFields(IgnoreExtras, Fields{ "Images": BeEmpty(), "HasPlaylist": BeFalse(), - "AudioFilesCount": BeNumerically("==", 5), + "AudioFilesCount": BeNumerically("==", 6), })) Expect(collected[filepath.Join(baseDir, "artist", "an-album")]).To(MatchFields(IgnoreExtras, Fields{ "Images": ConsistOf("cover.jpg", "front.png", "artist.png"), diff --git a/tests/fixtures/01 Invisible (RED) Edit Version.m4a b/tests/fixtures/01 Invisible (RED) Edit Version.m4a new file mode 100644 index 0000000000000000000000000000000000000000..55d360d89459e81d0b7944ae349dc6b223db5ece GIT binary patch literal 17011 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/test.mp3 b/tests/fixtures/test.mp3 index 49518e34f97516f67541a3ae76774f6709b7b3a4..610156f9a1de4f952a46b44f3125e2fdb2fa9bfa 100644 GIT binary patch delta 176 zcmZ27mHE;%<_#*R>Ky}ILmb^*BQCHrF!V7nFfao#5G(+bKmsTX2EGBt?v9>*@xGpZ zzK#(LhK7bZhGs@QP}v~Y03XLlu!4{vM`!PN5R*aMOwZWD001!zB%qWlnVf0Hon0cmMzZ delta 13 VcmcaKjd{sb<_#*RCSN+$001rx2K4{{ diff --git a/tests/fixtures/test.ogg b/tests/fixtures/test.ogg index be672812991e95e597a322eb3bb021f22143d087..9a5490d315dd6458598e4ce9f727ecadbf19ded9 100644 GIT binary patch delta 206 zcmX@9zDr|509)tJhsStsPYhFLWSdxJTCc#sz!2mb;NuwS?&#?k9}?u~>>VHA>ga82 zpl4`kZf<01U?zu64M@4Iwz-~}i9(7KnkkMxPNBX8Oo6E3MKNaMdT}l>mRyi&K=A1G eq0>wtnnh%t(&Rf_3QX2|oBwdRb4*SU&IAB!^f*ZX delta 92 zcmdm`aZ-Ik09!)&)>Iz