From c28dc78060a702d70ffb10c858d9565908089df1 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sun, 3 Jan 2021 17:27:35 +0100 Subject: [PATCH 01/10] Add more unit tests This not only increased the test coverage but also revealed some bugs which are fixed in the following commits. --- tests/test_apetag.cpp | 16 ++++ tests/test_fileref.cpp | 62 ++++++++++++++ tests/test_id3v1.cpp | 2 + tests/test_id3v2.cpp | 184 +++++++++++++++++++++++++++++++++++++++++ tests/test_mpeg.cpp | 88 ++++++++++++++++++++ 5 files changed, 352 insertions(+) diff --git a/tests/test_apetag.cpp b/tests/test_apetag.cpp index 577ec4b0..2225ccbd 100644 --- a/tests/test_apetag.cpp +++ b/tests/test_apetag.cpp @@ -79,6 +79,10 @@ public: CPPUNIT_ASSERT_EQUAL(2u, tag.itemListMap()["ARTIST"].values().size()); CPPUNIT_ASSERT_EQUAL(String("artist 1 artist 2"), tag.artist()); CPPUNIT_ASSERT_EQUAL(17u, tag.track()); + const APE::Item &textItem = tag.itemListMap()["TRACK"]; + CPPUNIT_ASSERT_EQUAL(APE::Item::Text, textItem.type()); + CPPUNIT_ASSERT(!textItem.isEmpty()); + CPPUNIT_ASSERT_EQUAL(9 + 5 + 2, textItem.size()); } void testPropertyInterface2() @@ -89,6 +93,8 @@ public: APE::Item item2 = APE::Item(); item2.setType(APE::Item::Binary); + ByteVector binaryData1("first"); + item2.setBinaryData(binaryData1); tag.setItem("TESTBINARY", item2); PropertyMap properties = tag.properties(); @@ -96,6 +102,16 @@ public: CPPUNIT_ASSERT(properties.contains("TRACKNUMBER")); CPPUNIT_ASSERT(!properties.contains("TRACK")); CPPUNIT_ASSERT(tag.itemListMap().contains("TESTBINARY")); + CPPUNIT_ASSERT_EQUAL(binaryData1, + tag.itemListMap()["TESTBINARY"].binaryData()); + ByteVector binaryData2("second"); + tag.setData("TESTBINARY", binaryData2); + const APE::Item &binaryItem = tag.itemListMap()["TESTBINARY"]; + CPPUNIT_ASSERT_EQUAL(APE::Item::Binary, binaryItem.type()); + CPPUNIT_ASSERT(!binaryItem.isEmpty()); + CPPUNIT_ASSERT_EQUAL(9 + 10 + static_cast(binaryData2.size()), + binaryItem.size()); + CPPUNIT_ASSERT_EQUAL(binaryData2, binaryItem.binaryData()); tag.removeUnsupportedProperties(properties.unsupportedData()); CPPUNIT_ASSERT(!tag.itemListMap().contains("TESTBINARY")); diff --git a/tests/test_fileref.cpp b/tests/test_fileref.cpp index 77f5f887..1fc5def9 100644 --- a/tests/test_fileref.cpp +++ b/tests/test_fileref.cpp @@ -39,6 +39,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -79,8 +82,12 @@ class TestFileRef : public CppUnit::TestFixture CPPUNIT_TEST(testWav); CPPUNIT_TEST(testAIFF_1); CPPUNIT_TEST(testAIFF_2); + CPPUNIT_TEST(testWavPack); + CPPUNIT_TEST(testOpus); CPPUNIT_TEST(testUnsupported); CPPUNIT_TEST(testCreate); + CPPUNIT_TEST(testAudioProperties); + CPPUNIT_TEST(testDefaultFileExtensions); CPPUNIT_TEST(testFileResolver); CPPUNIT_TEST_SUITE_END(); @@ -100,6 +107,7 @@ public: f.tag()->setTitle("test title"); f.tag()->setGenre("Test!"); f.tag()->setAlbum("albummmm"); + f.tag()->setComment("a comment"); f.tag()->setTrack(5); f.tag()->setYear(2020); f.save(); @@ -111,12 +119,14 @@ public: CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("test title")); CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("Test!")); CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("albummmm")); + CPPUNIT_ASSERT_EQUAL(f.tag()->comment(), String("a comment")); CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)5); CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2020); f.tag()->setArtist("ttest artist"); f.tag()->setTitle("ytest title"); f.tag()->setGenre("uTest!"); f.tag()->setAlbum("ialbummmm"); + f.tag()->setComment("another comment"); f.tag()->setTrack(7); f.tag()->setYear(2080); f.save(); @@ -128,6 +138,7 @@ public: CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("ytest title")); CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("uTest!")); CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("ialbummmm")); + CPPUNIT_ASSERT_EQUAL(f.tag()->comment(), String("another comment")); CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)7); CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2080); } @@ -141,12 +152,14 @@ public: CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("ytest title")); CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("uTest!")); CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("ialbummmm")); + CPPUNIT_ASSERT_EQUAL(f.tag()->comment(), String("another comment")); CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)7); CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2080); f.tag()->setArtist("test artist"); f.tag()->setTitle("test title"); f.tag()->setGenre("Test!"); f.tag()->setAlbum("albummmm"); + f.tag()->setComment("a comment"); f.tag()->setTrack(5); f.tag()->setYear(2020); f.save(); @@ -162,6 +175,7 @@ public: CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("test title")); CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("Test!")); CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("albummmm")); + CPPUNIT_ASSERT_EQUAL(f.tag()->comment(), String("a comment")); CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)5); CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2020); @@ -178,12 +192,14 @@ public: CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("test title")); CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("Test!")); CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("albummmm")); + CPPUNIT_ASSERT_EQUAL(f.tag()->comment(), String("a comment")); CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)5); CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2020); f.tag()->setArtist("ttest artist"); f.tag()->setTitle("ytest title"); f.tag()->setGenre("uTest!"); f.tag()->setAlbum("ialbummmm"); + f.tag()->setComment("another comment"); f.tag()->setTrack(7); f.tag()->setYear(2080); f.save(); @@ -199,6 +215,7 @@ public: CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("ytest title")); CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("uTest!")); CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("ialbummmm")); + CPPUNIT_ASSERT_EQUAL(f.tag()->comment(), String("another comment")); CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)7); CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2080); } @@ -289,6 +306,16 @@ public: fileRefSave("alaw", ".aifc"); } + void testWavPack() + { + fileRefSave("click", ".wv"); + } + + void testOpus() + { + fileRefSave("correctness_gain_silent_output", ".opus"); + } + void testUnsupported() { FileRef f1(TEST_FILE_PATH_C("no-extension")); @@ -309,6 +336,41 @@ public: f = FileRef::create(TEST_FILE_PATH_C("xing.mp3")); CPPUNIT_ASSERT(dynamic_cast(f)); delete f; + + f = FileRef::create(TEST_FILE_PATH_C("test.xm")); + CPPUNIT_ASSERT(dynamic_cast(f)); + delete f; + } + + void testAudioProperties() + { + FileRef f(TEST_FILE_PATH_C("xing.mp3")); + const AudioProperties *audioProperties = f.audioProperties(); + CPPUNIT_ASSERT_EQUAL(2, audioProperties->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(2064, audioProperties->lengthInMilliseconds()); + } + + void testDefaultFileExtensions() + { + const StringList extensions = FileRef::defaultFileExtensions(); + CPPUNIT_ASSERT(extensions.contains("mpc")); + CPPUNIT_ASSERT(extensions.contains("wma")); + CPPUNIT_ASSERT(extensions.contains("ogg")); + CPPUNIT_ASSERT(extensions.contains("spx")); + CPPUNIT_ASSERT(extensions.contains("flac")); + CPPUNIT_ASSERT(extensions.contains("mp3")); + CPPUNIT_ASSERT(extensions.contains("tta")); + CPPUNIT_ASSERT(extensions.contains("m4a")); + CPPUNIT_ASSERT(extensions.contains("3g2")); + CPPUNIT_ASSERT(extensions.contains("m4v")); + CPPUNIT_ASSERT(extensions.contains("wav")); + CPPUNIT_ASSERT(extensions.contains("oga")); + CPPUNIT_ASSERT(extensions.contains("ape")); + CPPUNIT_ASSERT(extensions.contains("aiff")); + CPPUNIT_ASSERT(extensions.contains("aifc")); + CPPUNIT_ASSERT(extensions.contains("wv")); + CPPUNIT_ASSERT(extensions.contains("opus")); + CPPUNIT_ASSERT(extensions.contains("xm")); } void testFileResolver() diff --git a/tests/test_id3v1.cpp b/tests/test_id3v1.cpp index c50c3428..d3f037aa 100644 --- a/tests/test_id3v1.cpp +++ b/tests/test_id3v1.cpp @@ -67,6 +67,8 @@ public: { CPPUNIT_ASSERT_EQUAL(String("Darkwave"), ID3v1::genre(50)); CPPUNIT_ASSERT_EQUAL(100, ID3v1::genreIndex("Humour")); + CPPUNIT_ASSERT(ID3v1::genreList().contains("Heavy Metal")); + CPPUNIT_ASSERT_EQUAL(79, ID3v1::genreMap()["Hard Rock"]); } void testRenamedGenres() diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 243e96ca..f03c82cc 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -42,6 +42,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -76,16 +79,20 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testParseAPIC); CPPUNIT_TEST(testParseAPIC_UTF16_BOM); CPPUNIT_TEST(testParseAPICv22); + CPPUNIT_TEST(testRenderAPIC); CPPUNIT_TEST(testDontRender22); CPPUNIT_TEST(testParseGEOB); + CPPUNIT_TEST(testRenderGEOB); CPPUNIT_TEST(testPOPMtoString); CPPUNIT_TEST(testParsePOPM); CPPUNIT_TEST(testParsePOPMWithoutCounter); CPPUNIT_TEST(testRenderPOPM); CPPUNIT_TEST(testPOPMFromFile); CPPUNIT_TEST(testParseRelativeVolumeFrame); + CPPUNIT_TEST(testRenderRelativeVolumeFrame); CPPUNIT_TEST(testParseUniqueFileIdentifierFrame); CPPUNIT_TEST(testParseEmptyUniqueFileIdentifierFrame); + CPPUNIT_TEST(testRenderUniqueFileIdentifierFrame); CPPUNIT_TEST(testBrokenFrame1); CPPUNIT_TEST(testItunes24FrameSize); CPPUNIT_TEST(testParseUrlLinkFrame); @@ -99,6 +106,12 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testRenderSynchronizedLyricsFrame); CPPUNIT_TEST(testParseEventTimingCodesFrame); CPPUNIT_TEST(testRenderEventTimingCodesFrame); + CPPUNIT_TEST(testParseCommentsFrame); + CPPUNIT_TEST(testRenderCommentsFrame); + CPPUNIT_TEST(testParsePodcastFrame); + CPPUNIT_TEST(testRenderPodcastFrame); + CPPUNIT_TEST(testParsePrivateFrame); + CPPUNIT_TEST(testRenderPrivateFrame); CPPUNIT_TEST(testSaveUTF16Comment); CPPUNIT_TEST(testUpdateGenre23_1); CPPUNIT_TEST(testUpdateGenre23_2); @@ -266,6 +279,26 @@ public: delete frame; } + void testRenderAPIC() + { + ID3v2::AttachedPictureFrame f; + f.setTextEncoding(String::UTF8); + f.setMimeType("image/png"); + f.setType(ID3v2::AttachedPictureFrame::BackCover); + f.setDescription("Description"); + f.setPicture("PNG data"); + CPPUNIT_ASSERT_EQUAL( + ByteVector("APIC" + "\x00\x00\x00\x20" + "\x00\x00" + "\x03" + "image/png\x00" + "\x04" + "Description\x00" + "PNG data", 42), + f.render()); + } + void testDontRender22() { ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance(); @@ -304,6 +337,26 @@ public: CPPUNIT_ASSERT_EQUAL(String("d"), f.description()); } + void testRenderGEOB() + { + ID3v2::GeneralEncapsulatedObjectFrame f; + f.setTextEncoding(String::Latin1); + f.setMimeType("application/octet-stream"); + f.setFileName("test.bin"); + f.setDescription("Description"); + f.setObject(ByteVector(3, '\x01')); + CPPUNIT_ASSERT_EQUAL( + ByteVector("GEOB" + "\x00\x00\x00\x32" + "\x00\x00" + "\x00" + "application/octet-stream\x00" + "test.bin\x00" + "Description\x00" + "\x01\x01\x01", 60), + f.render()); + } + void testParsePOPM() { ID3v2::PopularimeterFrame f(ByteVector("POPM" @@ -392,10 +445,36 @@ public: CPPUNIT_ASSERT_EQUAL(String("ident"), f.identification()); CPPUNIT_ASSERT_EQUAL(15.0f / 512.0f, f.volumeAdjustment(ID3v2::RelativeVolumeFrame::FrontRight)); + CPPUNIT_ASSERT_EQUAL(static_cast(15), + f.volumeAdjustmentIndex(ID3v2::RelativeVolumeFrame::FrontRight)); CPPUNIT_ASSERT_EQUAL((unsigned char)8, f.peakVolume(ID3v2::RelativeVolumeFrame::FrontRight).bitsRepresentingPeak); CPPUNIT_ASSERT_EQUAL(ByteVector("\x45"), f.peakVolume(ID3v2::RelativeVolumeFrame::FrontRight).peakVolume); + const List channels = f.channels(); + CPPUNIT_ASSERT_EQUAL(1U, channels.size()); + CPPUNIT_ASSERT_EQUAL(ID3v2::RelativeVolumeFrame::FrontRight, channels[0]); + } + + void testRenderRelativeVolumeFrame() + { + ID3v2::RelativeVolumeFrame f; + f.setIdentification("ident"); + f.setVolumeAdjustment(15.0f / 512.0f, ID3v2::RelativeVolumeFrame::FrontRight); + ID3v2::RelativeVolumeFrame::PeakVolume peakVolume; + peakVolume.bitsRepresentingPeak = 8; + peakVolume.peakVolume.setData("\x45"); + f.setPeakVolume(peakVolume, ID3v2::RelativeVolumeFrame::FrontRight); + CPPUNIT_ASSERT_EQUAL( + ByteVector("RVA2" + "\x00\x00\x00\x0B" + "\x00\x00" + "ident\x00" + "\x02" + "\x00\x0F" + "\x08" + "\x45", 21), + f.render()); } void testParseUniqueFileIdentifierFrame() @@ -426,6 +505,18 @@ public: f.identifier()); } + void testRenderUniqueFileIdentifierFrame() + { + ID3v2::UniqueFileIdentifierFrame f("owner", "\x01\x02\x03"); + CPPUNIT_ASSERT_EQUAL( + ByteVector("UFID" + "\x00\x00\x00\x09" + "\x00\x00" + "owner\x00" + "\x01\x02\x03", 19), + f.render()); + } + void testParseUrlLinkFrame() { ID3v2::UrlLinkFrame f( @@ -635,6 +726,89 @@ public: f.render()); } + void testParseCommentsFrame() + { + ID3v2::CommentsFrame f( + ByteVector("COMM" + "\x00\x00\x00\x14" + "\x00\x00" + "\x03" + "deu" + "Description\x00" + "Text", 30)); + CPPUNIT_ASSERT_EQUAL(String::UTF8, f.textEncoding()); + CPPUNIT_ASSERT_EQUAL(ByteVector("deu"), f.language()); + CPPUNIT_ASSERT_EQUAL(String("Description"), f.description()); + CPPUNIT_ASSERT_EQUAL(String("Text"), f.text()); + } + + void testRenderCommentsFrame() + { + ID3v2::CommentsFrame f; + f.setTextEncoding(String::UTF16); + f.setLanguage("eng"); + f.setDescription("Description"); + f.setText("Text"); + CPPUNIT_ASSERT_EQUAL( + ByteVector("COMM" + "\x00\x00\x00\x28" + "\x00\x00" + "\x01" + "eng" + "\xff\xfe" "D\0e\0s\0c\0r\0i\0p\0t\0i\0o\0n\0" "\x00\x00" + "\xff\xfe" "T\0e\0x\0t\0", 50), + f.render()); + } + + void testParsePodcastFrame() + { + ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance(); + ByteVector data = ByteVector("PCST" + "\x00\x00\x00\x04" + "\x00\x00" + "\x00\x00\x00\x00", 14); + const ID3v2::Header header; + CPPUNIT_ASSERT(dynamic_cast( + factory->createFrame(data, &header))); + } + + void testRenderPodcastFrame() + { + ID3v2::PodcastFrame f; + CPPUNIT_ASSERT_EQUAL( + ByteVector("PCST" + "\x00\x00\x00\x04" + "\x00\x00" + "\x00\x00\x00\x00", 14), + f.render()); + } + + void testParsePrivateFrame() + { + ID3v2::PrivateFrame f( + ByteVector("PRIV" + "\x00\x00\x00\x0e" + "\x00\x00" + "WM/Provider\x00" + "TL", 24)); + CPPUNIT_ASSERT_EQUAL(String("WM/Provider"), f.owner()); + CPPUNIT_ASSERT_EQUAL(ByteVector("TL"), f.data()); + } + + void testRenderPrivateFrame() + { + ID3v2::PrivateFrame f; + f.setOwner("WM/Provider"); + f.setData("TL"); + CPPUNIT_ASSERT_EQUAL( + ByteVector("PRIV" + "\x00\x00\x00\x0e" + "\x00\x00" + "WM/Provider\x00" + "TL", 24), + f.render()); + } + void testItunes24FrameSize() { MPEG::File f(TEST_FILE_PATH_C("005411.id3"), false); @@ -1246,6 +1420,16 @@ public: CPPUNIT_ASSERT((unsigned int)0x01 == f.embeddedFrameList().size()); CPPUNIT_ASSERT(f.embeddedFrameList("TIT2").size() == 1); CPPUNIT_ASSERT(f.embeddedFrameList("TIT2")[0]->toString() == "TC1"); + + f.removeChildElement("E"); // not existing + CPPUNIT_ASSERT_EQUAL(2U, f.entryCount()); + f.removeChildElement("C"); + CPPUNIT_ASSERT_EQUAL(1U, f.entryCount()); + CPPUNIT_ASSERT_EQUAL(ByteVector("D"), f.childElements()[0]); + + ID3v2::Frame *frame = f.embeddedFrameList("TIT2")[0]; + f.removeEmbeddedFrame(frame); + CPPUNIT_ASSERT(f.embeddedFrameList("TIT2").isEmpty()); } void testRenderTableOfContentsFrame() diff --git a/tests/test_mpeg.cpp b/tests/test_mpeg.cpp index 240231a4..f3b5c365 100644 --- a/tests/test_mpeg.cpp +++ b/tests/test_mpeg.cpp @@ -58,6 +58,7 @@ class TestMPEG : public CppUnit::TestFixture CPPUNIT_TEST(testFuzzedFile); CPPUNIT_TEST(testFrameOffset); CPPUNIT_TEST(testStripAndProperties); + CPPUNIT_TEST(testProperties); CPPUNIT_TEST(testRepeatedSave1); CPPUNIT_TEST(testRepeatedSave2); CPPUNIT_TEST(testRepeatedSave3); @@ -295,6 +296,93 @@ public: } } + void testProperties() + { + PropertyMap tags; + tags["ALBUM"] = StringList("Album"); + tags["ALBUMARTIST"] = StringList("Album Artist"); + tags["ALBUMARTISTSORT"] = StringList("Album Artist Sort"); + tags["ALBUMSORT"] = StringList("Album Sort"); + tags["ARRANGER"] = StringList("Arranger"); + tags["ARTIST"] = StringList("Artist"); + tags["ARTISTWEBPAGE"] = StringList("Artist Web Page"); + tags["AUDIOSOURCEWEBPAGE"] = StringList("Audio Source Web Page"); + tags["BPM"] = StringList("123"); + tags["COMMENT"] = StringList("Comment"); + tags["COMMENT:CDESC"] = StringList("Comment with Description"); + tags["COMPOSER"] = StringList("Composer"); + tags["CONDUCTOR"] = StringList("Conductor"); + tags["CONTENTGROUP"] = StringList("Content Group"); + tags["COPYRIGHT"] = StringList("2021 Copyright"); + tags["COPYRIGHTURL"] = StringList("Copyright URL"); + tags["DATE"] = StringList("2021-01-03 12:29:23"); + tags["DISCNUMBER"] = StringList("3/5"); + tags["DJMIXER"] = StringList("DJ Mixer"); + tags["ENCODEDBY"] = StringList("Encoded by"); + tags["ENCODING"] = StringList("Encoding"); + tags["ENCODINGTIME"] = StringList("2021-01-03 13:48:44"); + tags["ENGINEER"] = StringList("Engineer"); + tags["FILETYPE"] = StringList("File Type"); + tags["FILEWEBPAGE"] = StringList("File Web Page"); + tags["GENRE"] = StringList("Genre"); + tags["GROUPING"] = StringList("Grouping"); + tags["INITIALKEY"] = StringList("Dbm"); + tags["ISRC"] = StringList("UKAAA0500001"); + tags["LABEL"] = StringList("Label"); + tags["LANGUAGE"] = StringList("eng"); + tags["LENGTH"] = StringList("1234"); + tags["LYRICIST"] = StringList("Lyricist"); + tags["LYRICS:LDESC"] = StringList("Lyrics"); + tags["MEDIA"] = StringList("Media"); + tags["MIXER"] = StringList("Mixer"); + tags["MOOD"] = StringList("Mood"); + tags["MOVEMENTNAME"] = StringList("Movement Name"); + tags["MOVEMENTNUMBER"] = StringList("2"); + tags["ORIGINALALBUM"] = StringList("Original Album"); + tags["ORIGINALARTIST"] = StringList("Original Artist"); + tags["ORIGINALDATE"] = StringList("2021-01-03 13:52:19"); + tags["ORIGINALFILENAME"] = StringList("Original Filename"); + tags["ORIGINALLYRICIST"] = StringList("Original Lyricist"); + tags["OWNER"] = StringList("Owner"); + tags["PAYMENTWEBPAGE"] = StringList("Payment Web Page"); + tags["PERFORMER:DRUMS"] = StringList("Drummer"); + tags["PERFORMER:GUITAR"] = StringList("Guitarist"); + tags["PLAYLISTDELAY"] = StringList("10"); + tags["PODCAST"] = StringList(); + tags["PODCASTCATEGORY"] = StringList("Podcast Category"); + tags["PODCASTDESC"] = StringList("Podcast Description"); + tags["PODCASTID"] = StringList("Podcast ID"); + tags["PODCASTURL"] = StringList("Podcast URL"); + tags["PRODUCEDNOTICE"] = StringList("2021 Produced Notice"); + tags["PRODUCER"] = StringList("Producer"); + tags["PUBLISHERWEBPAGE"] = StringList("Publisher Web Page"); + tags["RADIOSTATION"] = StringList("Radio Station"); + tags["RADIOSTATIONOWNER"] = StringList("Radio Station Owner"); + tags["REMIXER"] = StringList("Remixer"); + tags["SUBTITLE"] = StringList("Subtitle"); + tags["TITLE"] = StringList("Title"); + tags["TITLESORT"] = StringList("Title Sort"); + tags["TRACKNUMBER"] = StringList("2/4"); + tags["URL:UDESC"] = StringList("URL"); + + ScopedFileCopy copy("xing", ".mp3"); + { + MPEG::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + CPPUNIT_ASSERT(properties.isEmpty()); + f.setProperties(tags); + f.save(); + } + { + const MPEG::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + if (tags != properties) { + CPPUNIT_ASSERT_EQUAL(tags.toString(), properties.toString()); + } + CPPUNIT_ASSERT(tags == properties); + } + } + void testRepeatedSave1() { ScopedFileCopy copy("xing", ".mp3"); From f74b1664356c2899b7b1065b2cf748cdff8a13a4 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sun, 3 Jan 2021 18:47:49 +0100 Subject: [PATCH 02/10] Add missing extensions to FileRef::defaultFileExtensions() --- taglib/fileref.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/taglib/fileref.cpp b/taglib/fileref.cpp index ba8f4f59..0f392ec7 100644 --- a/taglib/fileref.cpp +++ b/taglib/fileref.cpp @@ -366,6 +366,7 @@ StringList FileRef::defaultFileExtensions() l.append("ogg"); l.append("flac"); l.append("oga"); + l.append("opus"); l.append("mp3"); l.append("mpc"); l.append("wv"); @@ -382,6 +383,8 @@ StringList FileRef::defaultFileExtensions() l.append("asf"); l.append("aif"); l.append("aiff"); + l.append("afc"); + l.append("aifc"); l.append("wav"); l.append("ape"); l.append("mod"); From f32d27973f9bd62714b4f42319bf9eb8a3c20a91 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sun, 3 Jan 2021 18:50:19 +0100 Subject: [PATCH 03/10] Use PCST and not TXXX:PODCAST frame for ID3v2 'PODCAST' property --- taglib/mpeg/id3v2/frames/podcastframe.cpp | 8 ++++++++ taglib/mpeg/id3v2/frames/podcastframe.h | 2 ++ taglib/mpeg/id3v2/id3v2frame.cpp | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/taglib/mpeg/id3v2/frames/podcastframe.cpp b/taglib/mpeg/id3v2/frames/podcastframe.cpp index 7285b968..7dfe471d 100644 --- a/taglib/mpeg/id3v2/frames/podcastframe.cpp +++ b/taglib/mpeg/id3v2/frames/podcastframe.cpp @@ -24,6 +24,7 @@ ***************************************************************************/ #include "podcastframe.h" +#include using namespace TagLib; using namespace ID3v2; @@ -55,6 +56,13 @@ String PodcastFrame::toString() const return String(); } +PropertyMap PodcastFrame::asProperties() const +{ + PropertyMap map; + map.insert("PODCAST", StringList()); + return map; +} + //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/mpeg/id3v2/frames/podcastframe.h b/taglib/mpeg/id3v2/frames/podcastframe.h index 7bbc2138..a71278c3 100644 --- a/taglib/mpeg/id3v2/frames/podcastframe.h +++ b/taglib/mpeg/id3v2/frames/podcastframe.h @@ -57,6 +57,8 @@ namespace TagLib { */ virtual String toString() const; + PropertyMap asProperties() const; + protected: // Reimplementations. diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index f2414b16..4799c88c 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -40,6 +40,7 @@ #include "frames/commentsframe.h" #include "frames/uniquefileidentifierframe.h" #include "frames/unknownframe.h" +#include "frames/podcastframe.h" using namespace TagLib; using namespace ID3v2; @@ -120,6 +121,8 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) // UrlLinkFrame* frame = new UrlLinkFrame(frameID); frame->setUrl(values.front()); return frame; + } else if(frameID == "PCST") { + return new PodcastFrame(); } } if(key == "MUSICBRAINZ_TRACKID" && values.size() == 1) { @@ -490,6 +493,8 @@ PropertyMap Frame::asProperties() const return dynamic_cast< const UnsynchronizedLyricsFrame* >(this)->asProperties(); else if(id == "UFID") return dynamic_cast< const UniqueFileIdentifierFrame* >(this)->asProperties(); + else if(id == "PCST") + return dynamic_cast< const PodcastFrame* >(this)->asProperties(); PropertyMap m; m.unsupportedData().append(id); return m; From 794a2a0b2bedda58b0352411b19e27462a4627fb Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sun, 3 Jan 2021 18:52:47 +0100 Subject: [PATCH 04/10] Correctly construct PrivateFrame from frame data Frame::setData() has to be used instead of PrivateFrame::setData(). --- taglib/mpeg/id3v2/frames/privateframe.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taglib/mpeg/id3v2/frames/privateframe.cpp b/taglib/mpeg/id3v2/frames/privateframe.cpp index be80c4df..4f55a3ca 100644 --- a/taglib/mpeg/id3v2/frames/privateframe.cpp +++ b/taglib/mpeg/id3v2/frames/privateframe.cpp @@ -55,7 +55,7 @@ PrivateFrame::PrivateFrame(const ByteVector &data) : Frame(data), d(new PrivateFramePrivate()) { - setData(data); + Frame::setData(data); } PrivateFrame::~PrivateFrame() From 4828a3b925299e35d099d8d4b22705528745ce15 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sun, 3 Jan 2021 18:54:53 +0100 Subject: [PATCH 05/10] Do not crash when removing non existing TableOfContentsFrame child --- taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index 937cb4d2..36016c71 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -168,7 +168,8 @@ void TableOfContentsFrame::removeChildElement(const ByteVector &cE) if(it == d->childElements.end()) it = d->childElements.find(cE + ByteVector("\0")); - d->childElements.erase(it); + if(it != d->childElements.end()) + d->childElements.erase(it); } const FrameListMap &TableOfContentsFrame::embeddedFrameListMap() const @@ -196,11 +197,14 @@ void TableOfContentsFrame::removeEmbeddedFrame(Frame *frame, bool del) { // remove the frame from the frame list FrameList::Iterator it = d->embeddedFrameList.find(frame); - d->embeddedFrameList.erase(it); + if(it != d->embeddedFrameList.end()) + d->embeddedFrameList.erase(it); // ...and from the frame list map - it = d->embeddedFrameListMap[frame->frameID()].find(frame); - d->embeddedFrameListMap[frame->frameID()].erase(it); + FrameList &mappedList = d->embeddedFrameListMap[frame->frameID()]; + it = mappedList.find(frame); + if(it != mappedList.end()) + mappedList.erase(it); // ...and delete as desired if(del) From 2c29fbeabbd2bd43c8ede9223993beab3edf623d Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sun, 3 Jan 2021 18:58:29 +0100 Subject: [PATCH 06/10] Use mapped roles instead of property keys for TIPL roles For an ID3v2 "DJMIXER" property, the "DJ-MIX" TIPL role must be used. For an ID3v2 "MIXER" property, the "MIX" TIPL role must be used. Otherwise it will not work when reading the tag and creating properties from the wrong TIPL roles. --- taglib/mpeg/id3v2/frames/textidentificationframe.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp index 9b1eacd1..39019e61 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp @@ -63,7 +63,10 @@ TextIdentificationFrame *TextIdentificationFrame::createTIPLFrame(const Property TextIdentificationFrame *frame = new TextIdentificationFrame("TIPL"); StringList l; for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it){ - l.append(it->first); + const String role = involvedPeopleMap()[it->first]; + if(role.isEmpty()) // should not happen + continue; + l.append(role); l.append(it->second.toString(",")); // comma-separated list of names } frame->setText(l); From e6c03c6de8c4e16c55b140793c8e2acaad33a593 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sun, 3 Jan 2021 19:04:03 +0100 Subject: [PATCH 07/10] Create an APE tag when reading a WavPack file without tags Now it is handled in the same way as for other tags with a TagUnion. Without this patch, WavPack files without tags cannot be edited via a FileRef. --- taglib/wavpack/wavpackfile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taglib/wavpack/wavpackfile.cpp b/taglib/wavpack/wavpackfile.cpp index 01bdba36..56b99393 100644 --- a/taglib/wavpack/wavpackfile.cpp +++ b/taglib/wavpack/wavpackfile.cpp @@ -263,7 +263,7 @@ void WavPack::File::read(bool readProperties) d->APELocation = d->APELocation + APE::Footer::size() - d->APESize; } - if(d->ID3v1Location >= 0) + if(d->ID3v1Location < 0) APETag(true); // Look for WavPack audio properties From 310c3bc043cae36b5bde46cf4e4a696c1b908caf Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sat, 9 Jan 2021 18:10:05 +0100 Subject: [PATCH 08/10] Add missing 'COMPOSERSORT' property for ID3v2 tags --- taglib/mpeg/id3v2/id3v2frame.cpp | 3 ++- taglib/toolkit/tpropertymap.h | 1 + tests/test_mpeg.cpp | 12 ++++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index 4799c88c..27a81ada 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -347,7 +347,7 @@ namespace { "TEXT", "LYRICIST" }, { "TFLT", "FILETYPE" }, //{ "TIPL", "INVOLVEDPEOPLE" }, handled separately - { "TIT1", "CONTENTGROUP" }, + { "TIT1", "CONTENTGROUP" }, // 'Work' in iTunes { "TIT2", "TITLE"}, { "TIT3", "SUBTITLE" }, { "TKEY", "INITIALKEY" }, @@ -372,6 +372,7 @@ namespace { "TRSN", "RADIOSTATION" }, { "TRSO", "RADIOSTATIONOWNER" }, { "TSOA", "ALBUMSORT" }, + { "TSOC", "COMPOSERSORT" }, { "TSOP", "ARTISTSORT" }, { "TSOT", "TITLESORT" }, { "TSO2", "ALBUMARTISTSORT" }, // non-standard, used by iTunes diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index 5933b89b..e747a1ce 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -68,6 +68,7 @@ namespace TagLib { * - ALBUMSORT * - ARTISTSORT * - ALBUMARTISTSORT + * - COMPOSERSORT * * Credits: * diff --git a/tests/test_mpeg.cpp b/tests/test_mpeg.cpp index f3b5c365..9df5c861 100644 --- a/tests/test_mpeg.cpp +++ b/tests/test_mpeg.cpp @@ -305,12 +305,17 @@ public: tags["ALBUMSORT"] = StringList("Album Sort"); tags["ARRANGER"] = StringList("Arranger"); tags["ARTIST"] = StringList("Artist"); + tags["ARTISTSORT"] = StringList("Artist Sort"); tags["ARTISTWEBPAGE"] = StringList("Artist Web Page"); + tags["ASIN"] = StringList("ASIN"); tags["AUDIOSOURCEWEBPAGE"] = StringList("Audio Source Web Page"); + tags["BARCODE"] = StringList("Barcode"); tags["BPM"] = StringList("123"); + tags["CATALOGNUMBER"] = StringList("Catalog Number"); tags["COMMENT"] = StringList("Comment"); tags["COMMENT:CDESC"] = StringList("Comment with Description"); tags["COMPOSER"] = StringList("Composer"); + tags["COMPOSERSORT"] = StringList("Composer Sort"); tags["CONDUCTOR"] = StringList("Conductor"); tags["CONTENTGROUP"] = StringList("Content Group"); tags["COPYRIGHT"] = StringList("2021 Copyright"); @@ -338,6 +343,12 @@ public: tags["MOOD"] = StringList("Mood"); tags["MOVEMENTNAME"] = StringList("Movement Name"); tags["MOVEMENTNUMBER"] = StringList("2"); + tags["MUSICBRAINZ_ALBUMID"] = StringList("MusicBrainz_AlbumID"); + tags["MUSICBRAINZ_ALBUMARTISTID"] = StringList("MusicBrainz_AlbumartistID"); + tags["MUSICBRAINZ_ARTISTID"] = StringList("MusicBrainz_ArtistID"); + tags["MUSICBRAINZ_RELEASEGROUPID"] = StringList("MusicBrainz_ReleasegroupID"); + tags["MUSICBRAINZ_TRACKID"] = StringList("MusicBrainz_TrackID"); + tags["MUSICBRAINZ_WORKID"] = StringList("MusicBrainz_WorkID"); tags["ORIGINALALBUM"] = StringList("Original Album"); tags["ORIGINALARTIST"] = StringList("Original Artist"); tags["ORIGINALDATE"] = StringList("2021-01-03 13:52:19"); @@ -359,6 +370,7 @@ public: tags["RADIOSTATION"] = StringList("Radio Station"); tags["RADIOSTATIONOWNER"] = StringList("Radio Station Owner"); tags["REMIXER"] = StringList("Remixer"); + tags["SCRIPT"] = StringList("Script"); tags["SUBTITLE"] = StringList("Subtitle"); tags["TITLE"] = StringList("Title"); tags["TITLESORT"] = StringList("Title Sort"); From f7c24930cd9e8a02e542d97d98d9fd64cb6d0d73 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sat, 9 Jan 2021 18:12:13 +0100 Subject: [PATCH 09/10] Add missing iTunes properties for MP4 tags --- taglib/mp4/mp4tag.cpp | 25 +++++++++++-- tests/test_mp4.cpp | 86 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 4 deletions(-) diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp index 87ac1852..62ee3ace 100644 --- a/taglib/mp4/mp4tag.cpp +++ b/taglib/mp4/mp4tag.cpp @@ -895,6 +895,17 @@ namespace { "soco", "COMPOSERSORT" }, { "sosn", "SHOWSORT" }, { "shwm", "SHOWWORKMOVEMENT" }, + { "pgap", "GAPLESSPLAYBACK" }, + { "pcst", "PODCAST" }, + { "catg", "PODCASTCATEGORY" }, + { "desc", "PODCASTDESC" }, + { "egid", "PODCASTID" }, + { "purl", "PODCASTURL" }, + { "tves", "TVEPISODE" }, + { "tven", "TVEPISODEID" }, + { "tvnn", "TVNETWORK" }, + { "tvsn", "TVSEASON" }, + { "tvsh", "TVSHOW" }, { "\251wrk", "WORK" }, { "\251mvn", "MOVEMENTNAME" }, { "\251mvi", "MOVEMENTNUMBER" }, @@ -952,10 +963,12 @@ PropertyMap MP4::Tag::properties() const } props[key] = value; } - else if(key == "BPM" || key == "MOVEMENTNUMBER" || key == "MOVEMENTCOUNT") { + else if(key == "BPM" || key == "MOVEMENTNUMBER" || key == "MOVEMENTCOUNT" || + key == "TVEPISODE" || key == "TVSEASON") { props[key] = String::number(it->second.toInt()); } - else if(key == "COMPILATION" || key == "SHOWWORKMOVEMENT") { + else if(key == "COMPILATION" || key == "SHOWWORKMOVEMENT" || + key == "GAPLESSPLAYBACK" || key == "PODCAST") { props[key] = String::number(it->second.toBool()); } else { @@ -1007,11 +1020,15 @@ PropertyMap MP4::Tag::setProperties(const PropertyMap &props) d->items[name] = MP4::Item(first, second); } } - else if((it->first == "BPM" || it->first == "MOVEMENTNUMBER" || it->first == "MOVEMENTCOUNT") && !it->second.isEmpty()) { + else if((it->first == "BPM" || it->first == "MOVEMENTNUMBER" || + it->first == "MOVEMENTCOUNT" || it->first == "TVEPISODE" || + it->first == "TVSEASON") && !it->second.isEmpty()) { int value = it->second.front().toInt(); d->items[name] = MP4::Item(value); } - else if((it->first == "COMPILATION" || it->first == "SHOWWORKMOVEMENT") && !it->second.isEmpty()) { + else if((it->first == "COMPILATION" || it->first == "SHOWWORKMOVEMENT" || + it->first == "GAPLESSPLAYBACK" || it->first == "PODCAST") && + !it->second.isEmpty()) { bool value = (it->second.front().toInt() != 0); d->items[name] = MP4::Item(value); } diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index ba38c0f0..71fea15e 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -59,6 +59,7 @@ class TestMP4 : public CppUnit::TestFixture CPPUNIT_TEST(testCovrWrite); CPPUNIT_TEST(testCovrRead2); CPPUNIT_TEST(testProperties); + CPPUNIT_TEST(testPropertiesAllSupported); CPPUNIT_TEST(testPropertiesMovement); CPPUNIT_TEST(testFuzzedFile); CPPUNIT_TEST(testRepeatedSave); @@ -426,6 +427,91 @@ public: f.setProperties(tags); } + void testPropertiesAllSupported() + { + PropertyMap tags; + tags["ALBUM"] = StringList("Album"); + tags["ALBUMARTIST"] = StringList("Album Artist"); + tags["ALBUMARTISTSORT"] = StringList("Album Artist Sort"); + tags["ALBUMSORT"] = StringList("Album Sort"); + tags["ARTIST"] = StringList("Artist"); + tags["ARTISTSORT"] = StringList("Artist Sort"); + tags["ASIN"] = StringList("ASIN"); + tags["BARCODE"] = StringList("Barcode"); + tags["BPM"] = StringList("123"); + tags["CATALOGNUMBER"] = StringList("Catalog Number"); + tags["COMMENT"] = StringList("Comment"); + tags["COMPILATION"] = StringList("1"); + tags["COMPOSER"] = StringList("Composer"); + tags["COMPOSERSORT"] = StringList("Composer Sort"); + tags["CONDUCTOR"] = StringList("Conductor"); + tags["COPYRIGHT"] = StringList("2021 Copyright"); + tags["DATE"] = StringList("2021-01-03 12:29:23"); + tags["DISCNUMBER"] = StringList("3/5"); + tags["DISCSUBTITLE"] = StringList("Disc Subtitle"); + tags["DJMIXER"] = StringList("DJ Mixer"); + tags["ENCODEDBY"] = StringList("Encoded by"); + tags["ENGINEER"] = StringList("Engineer"); + tags["GAPLESSPLAYBACK"] = StringList("1"); + tags["GENRE"] = StringList("Genre"); + tags["GROUPING"] = StringList("Grouping"); + tags["ISRC"] = StringList("UKAAA0500001"); + tags["LABEL"] = StringList("Label"); + tags["LANGUAGE"] = StringList("eng"); + tags["LICENSE"] = StringList("License"); + tags["LYRICIST"] = StringList("Lyricist"); + tags["LYRICS"] = StringList("Lyrics"); + tags["MEDIA"] = StringList("Media"); + tags["MIXER"] = StringList("Mixer"); + tags["MOOD"] = StringList("Mood"); + tags["MOVEMENTCOUNT"] = StringList("3"); + tags["MOVEMENTNAME"] = StringList("Movement Name"); + tags["MOVEMENTNUMBER"] = StringList("2"); + tags["MUSICBRAINZ_ALBUMARTISTID"] = StringList("MusicBrainz_AlbumartistID"); + tags["MUSICBRAINZ_ALBUMID"] = StringList("MusicBrainz_AlbumID"); + tags["MUSICBRAINZ_ARTISTID"] = StringList("MusicBrainz_ArtistID"); + tags["MUSICBRAINZ_RELEASEGROUPID"] = StringList("MusicBrainz_ReleasegroupID"); + tags["MUSICBRAINZ_TRACKID"] = StringList("MusicBrainz_TrackID"); + tags["MUSICBRAINZ_WORKID"] = StringList("MusicBrainz_WorkID"); + tags["PODCAST"] = StringList("1"); + tags["PODCASTCATEGORY"] = StringList("Podcast Category"); + tags["PODCASTDESC"] = StringList("Podcast Description"); + tags["PODCASTID"] = StringList("Podcast ID"); + tags["PODCASTURL"] = StringList("Podcast URL"); + tags["PRODUCER"] = StringList("Producer"); + tags["REMIXER"] = StringList("Remixer"); + tags["SCRIPT"] = StringList("Script"); + tags["SHOWSORT"] = StringList("Show Sort"); + tags["SHOWWORKMOVEMENT"] = StringList("1"); + tags["SUBTITLE"] = StringList("Subtitle"); + tags["TITLE"] = StringList("Title"); + tags["TITLESORT"] = StringList("Title Sort"); + tags["TRACKNUMBER"] = StringList("2/4"); + tags["TVEPISODE"] = StringList("3"); + tags["TVEPISODEID"] = StringList("TV Episode ID"); + tags["TVNETWORK"] = StringList("TV Network"); + tags["TVSEASON"] = StringList("2"); + tags["TVSHOW"] = StringList("TV Show"); + tags["WORK"] = StringList("Work"); + + ScopedFileCopy copy("no-tags", ".m4a"); + { + MP4::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + CPPUNIT_ASSERT(properties.isEmpty()); + f.setProperties(tags); + f.save(); + } + { + const MP4::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + if (tags != properties) { + CPPUNIT_ASSERT_EQUAL(tags.toString(), properties.toString()); + } + CPPUNIT_ASSERT(tags == properties); + } + } + void testPropertiesMovement() { MP4::File f(TEST_FILE_PATH_C("has-tags.m4a")); From cf6c68bafc3246773a93e2cdeeb01ad5836150f0 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sun, 10 Jan 2021 15:16:45 +0100 Subject: [PATCH 10/10] Support a consistent set of MusicBrainz properties where possible The support for MusicBrainz properties is enhanced with "ARTISTS", "ASIN", "RELEASECOUNTRY", "RELEASESTATUS", "RELEASETYPE", "MUSICBRAINZ_RELEASETRACKID", "ORIGINALDATE" on APE, ASF, MP4, ID3v2, and Xiph tags. --- taglib/ape/apetag.cpp | 4 +- taglib/asf/asftag.cpp | 8 +++- taglib/mp4/mp4tag.cpp | 6 +++ taglib/mpeg/id3v2/id3v2frame.cpp | 4 ++ taglib/toolkit/tpropertymap.h | 4 ++ tests/test_ape.cpp | 52 +++++++++++++++++++++ tests/test_asf.cpp | 79 ++++++++++++++++++++++++++++++++ tests/test_flac.cpp | 55 ++++++++++++++++++++++ tests/test_mp4.cpp | 6 +++ tests/test_mpeg.cpp | 4 ++ 10 files changed, 220 insertions(+), 2 deletions(-) diff --git a/taglib/ape/apetag.cpp b/taglib/ape/apetag.cpp index 79e1d5cc..a2bdaeed 100644 --- a/taglib/ape/apetag.cpp +++ b/taglib/ape/apetag.cpp @@ -215,7 +215,9 @@ namespace {"DATE", "YEAR" }, {"ALBUMARTIST", "ALBUM ARTIST"}, {"DISCNUMBER", "DISC" }, - {"REMIXER", "MIXARTIST" }}; + {"REMIXER", "MIXARTIST" }, + {"RELEASESTATUS", "MUSICBRAINZ_ALBUMSTATUS" }, + {"RELEASETYPE", "MUSICBRAINZ_ALBUMTYPE" }}; const size_t keyConversionsSize = sizeof(keyConversions) / sizeof(keyConversions[0]); } diff --git a/taglib/asf/asftag.cpp b/taglib/asf/asftag.cpp index 20a946f0..935db046 100644 --- a/taglib/asf/asftag.cpp +++ b/taglib/asf/asftag.cpp @@ -216,7 +216,7 @@ namespace { "WM/AlbumTitle", "ALBUM" }, { "WM/AlbumArtist", "ALBUMARTIST" }, { "WM/Composer", "COMPOSER" }, - { "WM/Writer", "WRITER" }, + { "WM/Writer", "LYRICIST" }, { "WM/Conductor", "CONDUCTOR" }, { "WM/ModifiedBy", "REMIXER" }, { "WM/Year", "DATE" }, @@ -243,11 +243,17 @@ namespace { "WM/TitleSortOrder", "TITLESORT" }, { "WM/Script", "SCRIPT" }, { "WM/Language", "LANGUAGE" }, + { "WM/ARTISTS", "ARTISTS" }, + { "ASIN", "ASIN" }, { "MusicBrainz/Track Id", "MUSICBRAINZ_TRACKID" }, { "MusicBrainz/Artist Id", "MUSICBRAINZ_ARTISTID" }, { "MusicBrainz/Album Id", "MUSICBRAINZ_ALBUMID" }, { "MusicBrainz/Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" }, + { "MusicBrainz/Album Release Country", "RELEASECOUNTRY" }, + { "MusicBrainz/Album Status", "RELEASESTATUS" }, + { "MusicBrainz/Album Type", "RELEASETYPE" }, { "MusicBrainz/Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" }, + { "MusicBrainz/Release Track Id", "MUSICBRAINZ_RELEASETRACKID" }, { "MusicBrainz/Work Id", "MUSICBRAINZ_WORKID" }, { "MusicIP/PUID", "MUSICIP_PUID" }, { "Acoustid/Id", "ACOUSTID_ID" }, diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp index 62ee3ace..a99b7101 100644 --- a/taglib/mp4/mp4tag.cpp +++ b/taglib/mp4/mp4tag.cpp @@ -915,7 +915,13 @@ namespace { "----:com.apple.iTunes:MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" }, { "----:com.apple.iTunes:MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" }, { "----:com.apple.iTunes:MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" }, + { "----:com.apple.iTunes:MusicBrainz Release Track Id", "MUSICBRAINZ_RELEASETRACKID" }, { "----:com.apple.iTunes:MusicBrainz Work Id", "MUSICBRAINZ_WORKID" }, + { "----:com.apple.iTunes:MusicBrainz Album Release Country", "RELEASECOUNTRY" }, + { "----:com.apple.iTunes:MusicBrainz Album Status", "RELEASESTATUS" }, + { "----:com.apple.iTunes:MusicBrainz Album Type", "RELEASETYPE" }, + { "----:com.apple.iTunes:ARTISTS", "ARTISTS" }, + { "----:com.apple.iTunes:originaldate", "ORIGINALDATE" }, { "----:com.apple.iTunes:ASIN", "ASIN" }, { "----:com.apple.iTunes:LABEL", "LABEL" }, { "----:com.apple.iTunes:LYRICIST", "LYRICIST" }, diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index 27a81ada..81f351ef 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -406,7 +406,11 @@ namespace { "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID" }, { "MUSICBRAINZ ARTIST ID", "MUSICBRAINZ_ARTISTID" }, { "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ_ALBUMARTISTID" }, + { "MUSICBRAINZ ALBUM RELEASE COUNTRY", "RELEASECOUNTRY" }, + { "MUSICBRAINZ ALBUM STATUS", "RELEASESTATUS" }, + { "MUSICBRAINZ ALBUM TYPE", "RELEASETYPE" }, { "MUSICBRAINZ RELEASE GROUP ID", "MUSICBRAINZ_RELEASEGROUPID" }, + { "MUSICBRAINZ RELEASE TRACK ID", "MUSICBRAINZ_RELEASETRACKID" }, { "MUSICBRAINZ WORK ID", "MUSICBRAINZ_WORKID" }, { "ACOUSTID ID", "ACOUSTID_ID" }, { "ACOUSTID FINGERPRINT", "ACOUSTID_FINGERPRINT" }, diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index e747a1ce..d491efe8 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -91,12 +91,16 @@ namespace TagLib { * - LABEL * - CATALOGNUMBER * - BARCODE + * - RELEASECOUNTRY + * - RELEASESTATUS + * - RELEASETYPE * * MusicBrainz identifiers: * * - MUSICBRAINZ_TRACKID * - MUSICBRAINZ_ALBUMID * - MUSICBRAINZ_RELEASEGROUPID + * - MUSICBRAINZ_RELEASETRACKID * - MUSICBRAINZ_WORKID * - MUSICBRAINZ_ARTISTID * - MUSICBRAINZ_ALBUMARTISTID diff --git a/tests/test_ape.cpp b/tests/test_ape.cpp index 8c120483..81b8510f 100644 --- a/tests/test_ape.cpp +++ b/tests/test_ape.cpp @@ -155,6 +155,58 @@ public: } } + void testProperties() + { + PropertyMap tags; + tags["ALBUM"] = StringList("Album"); + tags["ALBUMARTIST"] = StringList("Album Artist"); + tags["ALBUMARTISTSORT"] = StringList("Album Artist Sort"); + tags["ALBUMSORT"] = StringList("Album Sort"); + tags["ARTIST"] = StringList("Artist"); + tags["ARTISTS"] = StringList("Artists"); + tags["ARTISTSORT"] = StringList("Artist Sort"); + tags["ASIN"] = StringList("ASIN"); + tags["BARCODE"] = StringList("Barcode"); + tags["CATALOGNUMBER"] = StringList("Catalog Number 1").append("Catalog Number 2"); + tags["COMMENT"] = StringList("Comment"); + tags["DATE"] = StringList("2021-01-10"); + tags["DISCNUMBER"] = StringList("3/5"); + tags["GENRE"] = StringList("Genre"); + tags["ISRC"] = StringList("UKAAA0500001"); + tags["LABEL"] = StringList("Label 1").append("Label 2"); + tags["MEDIA"] = StringList("Media"); + tags["MUSICBRAINZ_ALBUMARTISTID"] = StringList("MusicBrainz_AlbumartistID"); + tags["MUSICBRAINZ_ALBUMID"] = StringList("MusicBrainz_AlbumID"); + tags["MUSICBRAINZ_ARTISTID"] = StringList("MusicBrainz_ArtistID"); + tags["MUSICBRAINZ_RELEASEGROUPID"] = StringList("MusicBrainz_ReleasegroupID"); + tags["MUSICBRAINZ_RELEASETRACKID"] = StringList("MusicBrainz_ReleasetrackID"); + tags["MUSICBRAINZ_TRACKID"] = StringList("MusicBrainz_TrackID"); + tags["ORIGINALDATE"] = StringList("2021-01-09"); + tags["RELEASECOUNTRY"] = StringList("Release Country"); + tags["RELEASESTATUS"] = StringList("Release Status"); + tags["RELEASETYPE"] = StringList("Release Type"); + tags["SCRIPT"] = StringList("Script"); + tags["TITLE"] = StringList("Title"); + tags["TRACKNUMBER"] = StringList("2/3"); + + ScopedFileCopy copy("mac-399", ".ape"); + { + APE::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + CPPUNIT_ASSERT(properties.isEmpty()); + f.setProperties(tags); + f.save(); + } + { + const APE::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + if (tags != properties) { + CPPUNIT_ASSERT_EQUAL(tags.toString(), properties.toString()); + } + CPPUNIT_ASSERT(tags == properties); + } + } + void testRepeatedSave() { ScopedFileCopy copy("mac-399", ".ape"); diff --git a/tests/test_asf.cpp b/tests/test_asf.cpp index e9b8dab7..2abe9fe5 100644 --- a/tests/test_asf.cpp +++ b/tests/test_asf.cpp @@ -50,6 +50,7 @@ class TestASF : public CppUnit::TestFixture CPPUNIT_TEST(testSavePicture); CPPUNIT_TEST(testSaveMultiplePictures); CPPUNIT_TEST(testProperties); + CPPUNIT_TEST(testPropertiesAllSupported); CPPUNIT_TEST(testRepeatedSave); CPPUNIT_TEST_SUITE_END(); @@ -302,6 +303,84 @@ public: CPPUNIT_ASSERT_EQUAL(StringList("3"), tags["DISCNUMBER"]); } + void testPropertiesAllSupported() + { + PropertyMap tags; + tags["ACOUSTID_ID"] = StringList("Acoustid ID"); + tags["ACOUSTID_FINGERPRINT"] = StringList("Acoustid Fingerprint"); + tags["ALBUM"] = StringList("Album"); + tags["ALBUMARTIST"] = StringList("Album Artist"); + tags["ALBUMARTISTSORT"] = StringList("Album Artist Sort"); + tags["ALBUMSORT"] = StringList("Album Sort"); + tags["ARTIST"] = StringList("Artist"); + tags["ARTISTS"] = StringList("Artists"); + tags["ARTISTSORT"] = StringList("Artist Sort"); + tags["ASIN"] = StringList("ASIN"); + tags["BARCODE"] = StringList("Barcode"); + tags["BPM"] = StringList("123"); + tags["CATALOGNUMBER"] = StringList("Catalog Number"); + tags["COMMENT"] = StringList("Comment"); + tags["COMPOSER"] = StringList("Composer"); + tags["CONDUCTOR"] = StringList("Conductor"); + tags["COPYRIGHT"] = StringList("2021 Copyright"); + tags["DATE"] = StringList("2021-01-03 12:29:23"); + tags["DISCNUMBER"] = StringList("3/5"); + tags["DISCSUBTITLE"] = StringList("Disc Subtitle"); + tags["ENCODEDBY"] = StringList("Encoded by"); + tags["GENRE"] = StringList("Genre"); + tags["GROUPING"] = StringList("Grouping"); + tags["ISRC"] = StringList("UKAAA0500001"); + tags["LABEL"] = StringList("Label"); + tags["LANGUAGE"] = StringList("eng"); + tags["LYRICIST"] = StringList("Lyricist"); + tags["LYRICS"] = StringList("Lyrics"); + tags["MEDIA"] = StringList("Media"); + tags["MOOD"] = StringList("Mood"); + tags["MUSICBRAINZ_ALBUMARTISTID"] = StringList("MusicBrainz_AlbumartistID"); + tags["MUSICBRAINZ_ALBUMID"] = StringList("MusicBrainz_AlbumID"); + tags["MUSICBRAINZ_ARTISTID"] = StringList("MusicBrainz_ArtistID"); + tags["MUSICBRAINZ_RELEASEGROUPID"] = StringList("MusicBrainz_ReleasegroupID"); + tags["MUSICBRAINZ_RELEASETRACKID"] = StringList("MusicBrainz_ReleasetrackID"); + tags["MUSICBRAINZ_TRACKID"] = StringList("MusicBrainz_TrackID"); + tags["MUSICBRAINZ_WORKID"] = StringList("MusicBrainz_WorkID"); + tags["MUSICIP_PUID"] = StringList("MusicIP PUID"); + tags["ORIGINALDATE"] = StringList("2021-01-03 13:52:19"); + tags["PRODUCER"] = StringList("Producer"); + tags["RELEASECOUNTRY"] = StringList("Release Country"); + tags["RELEASESTATUS"] = StringList("Release Status"); + tags["RELEASETYPE"] = StringList("Release Type"); + tags["REMIXER"] = StringList("Remixer"); + tags["SCRIPT"] = StringList("Script"); + tags["SUBTITLE"] = StringList("Subtitle"); + tags["TITLE"] = StringList("Title"); + tags["TITLESORT"] = StringList("Title Sort"); + tags["TRACKNUMBER"] = StringList("2/4"); + + ScopedFileCopy copy("silence-1", ".wma"); + { + ASF::File f(copy.fileName().c_str()); + ASF::Tag *asfTag = f.tag(); + asfTag->setTitle(""); + asfTag->attributeListMap().clear(); + f.save(); + } + { + ASF::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + CPPUNIT_ASSERT(properties.isEmpty()); + f.setProperties(tags); + f.save(); + } + { + const ASF::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + if (tags != properties) { + CPPUNIT_ASSERT_EQUAL(tags.toString(), properties.toString()); + } + CPPUNIT_ASSERT(tags == properties); + } + } + void testRepeatedSave() { ScopedFileCopy copy("silence-1", ".wma"); diff --git a/tests/test_flac.cpp b/tests/test_flac.cpp index 7c25a83b..c83f1e90 100644 --- a/tests/test_flac.cpp +++ b/tests/test_flac.cpp @@ -54,6 +54,7 @@ class TestFLAC : public CppUnit::TestFixture CPPUNIT_TEST(testRepeatedSave3); CPPUNIT_TEST(testSaveMultipleValues); CPPUNIT_TEST(testDict); + CPPUNIT_TEST(testProperties); CPPUNIT_TEST(testInvalid); CPPUNIT_TEST(testAudioProperties); CPPUNIT_TEST(testZeroSizedPadding1); @@ -313,6 +314,60 @@ public: } } + void testProperties() + { + PropertyMap tags; + tags["ALBUM"] = StringList("Album"); + tags["ALBUMARTIST"] = StringList("Album Artist"); + tags["ALBUMARTISTSORT"] = StringList("Album Artist Sort"); + tags["ALBUMSORT"] = StringList("Album Sort"); + tags["ARTIST"] = StringList("Artist"); + tags["ARTISTS"] = StringList("Artists"); + tags["ARTISTSORT"] = StringList("Artist Sort"); + tags["ASIN"] = StringList("ASIN"); + tags["BARCODE"] = StringList("Barcode"); + tags["CATALOGNUMBER"] = StringList("Catalog Number 1").append("Catalog Number 2"); + tags["COMMENT"] = StringList("Comment"); + tags["DATE"] = StringList("2021-01-10"); + tags["DISCNUMBER"] = StringList("3"); + tags["DISCTOTAL"] = StringList("5"); + tags["GENRE"] = StringList("Genre"); + tags["ISRC"] = StringList("UKAAA0500001"); + tags["LABEL"] = StringList("Label 1").append("Label 2"); + tags["MEDIA"] = StringList("Media"); + tags["MUSICBRAINZ_ALBUMARTISTID"] = StringList("MusicBrainz_AlbumartistID"); + tags["MUSICBRAINZ_ALBUMID"] = StringList("MusicBrainz_AlbumID"); + tags["MUSICBRAINZ_ARTISTID"] = StringList("MusicBrainz_ArtistID"); + tags["MUSICBRAINZ_RELEASEGROUPID"] = StringList("MusicBrainz_ReleasegroupID"); + tags["MUSICBRAINZ_RELEASETRACKID"] = StringList("MusicBrainz_ReleasetrackID"); + tags["MUSICBRAINZ_TRACKID"] = StringList("MusicBrainz_TrackID"); + tags["ORIGINALDATE"] = StringList("2021-01-09"); + tags["RELEASECOUNTRY"] = StringList("Release Country"); + tags["RELEASESTATUS"] = StringList("Release Status"); + tags["RELEASETYPE"] = StringList("Release Type"); + tags["SCRIPT"] = StringList("Script"); + tags["TITLE"] = StringList("Title"); + tags["TRACKNUMBER"] = StringList("2"); + tags["TRACKTOTAL"] = StringList("4"); + + ScopedFileCopy copy("no-tags", ".flac"); + { + FLAC::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + CPPUNIT_ASSERT(properties.isEmpty()); + f.setProperties(tags); + f.save(); + } + { + const FLAC::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + if (tags != properties) { + CPPUNIT_ASSERT_EQUAL(tags.toString(), properties.toString()); + } + CPPUNIT_ASSERT(tags == properties); + } + } + void testInvalid() { ScopedFileCopy copy("silence-44-s", ".flac"); diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index 71fea15e..5f96c9c0 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -435,6 +435,7 @@ public: tags["ALBUMARTISTSORT"] = StringList("Album Artist Sort"); tags["ALBUMSORT"] = StringList("Album Sort"); tags["ARTIST"] = StringList("Artist"); + tags["ARTISTS"] = StringList("Artists"); tags["ARTISTSORT"] = StringList("Artist Sort"); tags["ASIN"] = StringList("ASIN"); tags["BARCODE"] = StringList("Barcode"); @@ -471,14 +472,19 @@ public: tags["MUSICBRAINZ_ALBUMID"] = StringList("MusicBrainz_AlbumID"); tags["MUSICBRAINZ_ARTISTID"] = StringList("MusicBrainz_ArtistID"); tags["MUSICBRAINZ_RELEASEGROUPID"] = StringList("MusicBrainz_ReleasegroupID"); + tags["MUSICBRAINZ_RELEASETRACKID"] = StringList("MusicBrainz_ReleasetrackID"); tags["MUSICBRAINZ_TRACKID"] = StringList("MusicBrainz_TrackID"); tags["MUSICBRAINZ_WORKID"] = StringList("MusicBrainz_WorkID"); + tags["ORIGINALDATE"] = StringList("2021-01-03 13:52:19"); tags["PODCAST"] = StringList("1"); tags["PODCASTCATEGORY"] = StringList("Podcast Category"); tags["PODCASTDESC"] = StringList("Podcast Description"); tags["PODCASTID"] = StringList("Podcast ID"); tags["PODCASTURL"] = StringList("Podcast URL"); tags["PRODUCER"] = StringList("Producer"); + tags["RELEASECOUNTRY"] = StringList("Release Country"); + tags["RELEASESTATUS"] = StringList("Release Status"); + tags["RELEASETYPE"] = StringList("Release Type"); tags["REMIXER"] = StringList("Remixer"); tags["SCRIPT"] = StringList("Script"); tags["SHOWSORT"] = StringList("Show Sort"); diff --git a/tests/test_mpeg.cpp b/tests/test_mpeg.cpp index 9df5c861..1db1c9d4 100644 --- a/tests/test_mpeg.cpp +++ b/tests/test_mpeg.cpp @@ -347,6 +347,7 @@ public: tags["MUSICBRAINZ_ALBUMARTISTID"] = StringList("MusicBrainz_AlbumartistID"); tags["MUSICBRAINZ_ARTISTID"] = StringList("MusicBrainz_ArtistID"); tags["MUSICBRAINZ_RELEASEGROUPID"] = StringList("MusicBrainz_ReleasegroupID"); + tags["MUSICBRAINZ_RELEASETRACKID"] = StringList("MusicBrainz_ReleasetrackID"); tags["MUSICBRAINZ_TRACKID"] = StringList("MusicBrainz_TrackID"); tags["MUSICBRAINZ_WORKID"] = StringList("MusicBrainz_WorkID"); tags["ORIGINALALBUM"] = StringList("Original Album"); @@ -369,6 +370,9 @@ public: tags["PUBLISHERWEBPAGE"] = StringList("Publisher Web Page"); tags["RADIOSTATION"] = StringList("Radio Station"); tags["RADIOSTATIONOWNER"] = StringList("Radio Station Owner"); + tags["RELEASECOUNTRY"] = StringList("Release Country"); + tags["RELEASESTATUS"] = StringList("Release Status"); + tags["RELEASETYPE"] = StringList("Release Type"); tags["REMIXER"] = StringList("Remixer"); tags["SCRIPT"] = StringList("Script"); tags["SUBTITLE"] = StringList("Subtitle");