Tag Parser  7.1.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
overallmp3.cpp
Go to the documentation of this file.
1 #include "./helper.h"
2 #include "./overall.h"
3 
4 #include "../abstracttrack.h"
5 #include "../id3/id3v1tag.h"
6 #include "../id3/id3v2tag.h"
7 #include "../mpegaudio/mpegaudioframe.h"
8 
9 namespace Mp3TestFlags {
10 enum TestFlag {
11  ForceRewring = 0x1,
14  Id3v1Only = 0x8,
16  UseId3v24 = 0x10,
17 };
18 }
19 
23 void OverallTests::checkMp3Testfile1()
24 {
25  CPPUNIT_ASSERT_EQUAL(ContainerFormat::MpegAudioFrames, m_fileInfo.containerFormat());
26  const auto tracks = m_fileInfo.tracks();
27  CPPUNIT_ASSERT_EQUAL(1_st, tracks.size());
28  for (const auto &track : tracks) {
29  CPPUNIT_ASSERT_EQUAL(MediaType::Audio, track->mediaType());
30  CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::Mpeg1Audio, track->format().general);
31  CPPUNIT_ASSERT_EQUAL(static_cast<unsigned char>(SubFormats::Mpeg1Layer3), track->format().sub);
32  CPPUNIT_ASSERT_EQUAL(static_cast<uint16>(2), track->channelCount());
33  CPPUNIT_ASSERT_EQUAL(static_cast<byte>(MpegChannelMode::JointStereo), track->channelConfig());
34  CPPUNIT_ASSERT_EQUAL(44100u, track->samplingFrequency());
35  CPPUNIT_ASSERT_EQUAL(3, track->duration().seconds());
36  }
37  const auto tags = m_fileInfo.tags();
38  switch (m_tagStatus) {
40  CPPUNIT_ASSERT(m_fileInfo.id3v1Tag());
41  CPPUNIT_ASSERT_EQUAL(1_st, m_fileInfo.id3v2Tags().size());
42  CPPUNIT_ASSERT_EQUAL(2_st, tags.size());
43  for (const auto &tag : tags) {
44  CPPUNIT_ASSERT_EQUAL(4, tag->value(KnownField::TrackPosition).toPositionInSet().position());
45  CPPUNIT_ASSERT_EQUAL("1984"s, tag->value(KnownField::Year).toString());
46  switch (tag->type()) {
47  case TagType::Id3v1Tag:
48  CPPUNIT_ASSERT_EQUAL("Cohesion"s, tag->value(KnownField::Title).toString());
49  CPPUNIT_ASSERT_EQUAL("Minutemen"s, tag->value(KnownField::Artist).toString());
50  CPPUNIT_ASSERT_EQUAL("Double Nickels On The Dime"s, tag->value(KnownField::Album).toString());
51  CPPUNIT_ASSERT_EQUAL("Punk Rock"s, tag->value(KnownField::Genre).toString());
52  CPPUNIT_ASSERT_EQUAL("ExactAudioCopy v0.95b4"s, tag->value(KnownField::Comment).toString());
53  break;
54  case TagType::Id3v2Tag:
55  CPPUNIT_ASSERT_EQUAL(TagTextEncoding::Utf16LittleEndian, tag->value(KnownField::Title).dataEncoding());
56  CPPUNIT_ASSERT_EQUAL(u"Cohesion"s, tag->value(KnownField::Title).toWString());
57  CPPUNIT_ASSERT_EQUAL("Cohesion"s, tag->value(KnownField::Title).toString(TagTextEncoding::Utf8));
58  CPPUNIT_ASSERT_EQUAL(u"Minutemen"s, tag->value(KnownField::Artist).toWString());
59  CPPUNIT_ASSERT_EQUAL("Minutemen"s, tag->value(KnownField::Artist).toString(TagTextEncoding::Utf8));
60  CPPUNIT_ASSERT_EQUAL(u"Double Nickels On The Dime"s, tag->value(KnownField::Album).toWString());
61  CPPUNIT_ASSERT_EQUAL("Double Nickels On The Dime"s, tag->value(KnownField::Album).toString(TagTextEncoding::Utf8));
62  CPPUNIT_ASSERT_EQUAL(u"Punk Rock"s, tag->value(KnownField::Genre).toWString());
63  CPPUNIT_ASSERT_EQUAL("Punk Rock"s, tag->value(KnownField::Genre).toString(TagTextEncoding::Utf8));
64  CPPUNIT_ASSERT_EQUAL(u"ExactAudioCopy v0.95b4"s, tag->value(KnownField::Comment).toWString());
65  CPPUNIT_ASSERT_EQUAL("ExactAudioCopy v0.95b4"s, tag->value(KnownField::Comment).toString(TagTextEncoding::Utf8));
66  CPPUNIT_ASSERT_EQUAL(43, tag->value(KnownField::TrackPosition).toPositionInSet().total());
67  CPPUNIT_ASSERT(tag->value(KnownField::Length).toTimeSpan().isNull());
68  CPPUNIT_ASSERT(tag->value(KnownField::Lyricist).isEmpty());
69  break;
70  default:;
71  }
72  }
73  break;
75  checkMp3TestMetaData();
76  break;
77  case TagStatus::Removed:
78  CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
79  }
80 
81  CPPUNIT_ASSERT(m_diag.level() <= DiagLevel::Information);
82 }
83 
87 void OverallTests::checkMp3TestMetaData()
88 {
89  using namespace Mp3TestFlags;
90 
91  // check whether tags are assigned according to the current test mode
92  Id3v1Tag *id3v1Tag = nullptr;
93  Id3v2Tag *id3v2Tag = nullptr;
94  if (m_mode & Id3v2AndId3v1) {
95  CPPUNIT_ASSERT(id3v1Tag = m_fileInfo.id3v1Tag());
96  CPPUNIT_ASSERT(id3v2Tag = m_fileInfo.id3v2Tags().at(0).get());
97  } else if (m_mode & Id3v1Only) {
98  CPPUNIT_ASSERT(id3v1Tag = m_fileInfo.id3v1Tag());
99  CPPUNIT_ASSERT(m_fileInfo.id3v2Tags().empty());
100  } else {
101  CPPUNIT_ASSERT(!m_fileInfo.id3v1Tag());
102  CPPUNIT_ASSERT(id3v2Tag = m_fileInfo.id3v2Tags().at(0).get());
103  }
104 
105  // check common test meta data
106  if (id3v1Tag) {
107  CPPUNIT_ASSERT_EQUAL(m_testTitle, id3v1Tag->value(KnownField::Title));
108  CPPUNIT_ASSERT_EQUAL(m_testComment.toString(), id3v1Tag->value(KnownField::Comment).toString()); // ignore encoding here
109  CPPUNIT_ASSERT_EQUAL(m_testAlbum, id3v1Tag->value(KnownField::Album));
110  CPPUNIT_ASSERT_EQUAL(m_preservedMetaData.front(), id3v1Tag->value(KnownField::Artist));
111  m_preservedMetaData.pop();
112  }
113  if (id3v2Tag) {
114  const TagValue &titleValue = id3v2Tag->value(KnownField::Title);
115  const TagValue &commentValue = id3v2Tag->value(KnownField::Comment);
116 
117  if (m_mode & UseId3v24) {
118  CPPUNIT_ASSERT_EQUAL(m_testTitle, titleValue);
119  CPPUNIT_ASSERT_EQUAL(m_testComment, commentValue);
120  CPPUNIT_ASSERT_EQUAL(m_testAlbum, id3v2Tag->value(KnownField::Album));
121  CPPUNIT_ASSERT_EQUAL(m_preservedMetaData.front(), id3v2Tag->value(KnownField::Artist));
122  // TODO: check more fields
123  } else {
124  CPPUNIT_ASSERT_MESSAGE("not attempted to use UTF-8 in ID3v2.3", titleValue.dataEncoding() == TagTextEncoding::Utf16LittleEndian);
125  CPPUNIT_ASSERT_EQUAL(m_testTitle.toString(), titleValue.toString(TagTextEncoding::Utf8));
126  CPPUNIT_ASSERT_MESSAGE("not attempted to use UTF-8 in ID3v2.3", commentValue.dataEncoding() == TagTextEncoding::Utf16LittleEndian);
127  CPPUNIT_ASSERT_MESSAGE("not attempted to use UTF-8 in ID3v2.3", commentValue.descriptionEncoding() == TagTextEncoding::Utf16LittleEndian);
128  CPPUNIT_ASSERT_EQUAL(m_testComment.toString(), commentValue.toString(TagTextEncoding::Utf8));
129  CPPUNIT_ASSERT_EQUAL_MESSAGE(
130  "description is also converted to UTF-16", "s\0o\0m\0e\0 \0d\0e\0s\0c\0r\0i\0p\0t\0i\0\xf3\0n\0"s, commentValue.description());
131  CPPUNIT_ASSERT_EQUAL(m_testAlbum.toString(TagTextEncoding::Utf8), id3v2Tag->value(KnownField::Album).toString(TagTextEncoding::Utf8));
132  CPPUNIT_ASSERT_EQUAL(m_preservedMetaData.front(), id3v2Tag->value(KnownField::Artist));
133  // TODO: check more fields
134  }
135 
136  m_preservedMetaData.pop();
137  }
138 
139  // test ID3v1 specific test meta data
140  if (id3v1Tag) {
141  CPPUNIT_ASSERT_EQUAL(m_testPosition.toPositionInSet().position(), id3v1Tag->value(KnownField::TrackPosition).toPositionInSet().position());
142  }
143  // test ID3v2 specific test meta data
144  if (id3v2Tag) {
145  CPPUNIT_ASSERT_EQUAL(m_testPosition, id3v2Tag->value(KnownField::TrackPosition));
146  CPPUNIT_ASSERT_EQUAL(m_testPosition, id3v2Tag->value(KnownField::DiskPosition));
147  }
148 }
149 
153 void OverallTests::checkMp3PaddingConstraints()
154 {
155  using namespace Mp3TestFlags;
156 
157  if (!(m_mode & Id3v1Only)) {
158  if (m_mode & PaddingConstraints) {
159  if (m_mode & ForceRewring) {
160  CPPUNIT_ASSERT_EQUAL(static_cast<uint64>(4096), m_fileInfo.paddingSize());
161  } else {
162  CPPUNIT_ASSERT(m_fileInfo.paddingSize() >= 1024);
163  CPPUNIT_ASSERT(m_fileInfo.paddingSize() <= (4096 + 1024));
164  }
165  }
166  } else {
167  // adding padding is not possible if no ID3v2 tag is present
168  }
169  // TODO: check rewriting behaviour
170 }
171 
172 void OverallTests::setMp3TestMetaData()
173 {
174  using namespace Mp3TestFlags;
175 
176  // ensure tags are assigned according to the current test mode
177  Id3v1Tag *id3v1Tag = nullptr;
178  Id3v2Tag *id3v2Tag = nullptr;
179  if (m_mode & Id3v2AndId3v1) {
180  id3v1Tag = m_fileInfo.createId3v1Tag();
181  id3v2Tag = m_fileInfo.createId3v2Tag();
182  } else if (m_mode & Id3v1Only) {
183  id3v1Tag = m_fileInfo.createId3v1Tag();
184  m_fileInfo.removeAllId3v2Tags();
185  } else {
186  m_fileInfo.removeId3v1Tag();
187  id3v2Tag = m_fileInfo.createId3v2Tag();
188  }
189  if (!(m_mode & Id3v1Only) && m_mode & UseId3v24) {
190  id3v2Tag->setVersion(4, 0);
191  }
192 
193  // assign some test meta data
194  for (Tag *tag : initializer_list<Tag *>{ id3v1Tag, id3v2Tag }) {
195  if (tag) {
196  tag->setValue(KnownField::Title, m_testTitle);
197  tag->setValue(KnownField::Comment, m_testComment);
198  tag->setValue(KnownField::Album, m_testAlbum);
199  m_preservedMetaData.push(tag->value(KnownField::Artist));
200  tag->setValue(KnownField::TrackPosition, m_testPosition);
201  tag->setValue(KnownField::DiskPosition, m_testPosition);
202  // TODO: set more fields
203  }
204  }
205 }
206 
211 {
212  cerr << endl << "MP3 parser" << endl;
213  m_fileInfo.setForceFullParse(false);
214  m_tagStatus = TagStatus::Original;
215  parseFile(TestUtilities::testFilePath("mtx-test-data/mp3/id3-tag-and-xing-header.mp3"), &OverallTests::checkMp3Testfile1);
216 }
217 
218 #ifdef PLATFORM_UNIX
219 
223 void OverallTests::testMp3Making()
224 {
225  // full parse is required to determine padding
226  m_fileInfo.setForceFullParse(true);
227 
228  // do the test under different conditions
229  for (m_mode = 0; m_mode != 0x20; ++m_mode) {
230  using namespace Mp3TestFlags;
231 
232  // setup test conditions
233  m_fileInfo.setForceRewrite(m_mode & ForceRewring);
234  if (m_mode & UseId3v24) {
235  if (m_mode & Id3v1Only) {
236  continue;
237  }
238  }
239  m_fileInfo.setTagPosition(ElementPosition::Keep);
240  m_fileInfo.setIndexPosition(ElementPosition::Keep);
241  m_fileInfo.setPreferredPadding(m_mode & PaddingConstraints ? 4096 : 0);
242  m_fileInfo.setMinPadding(m_mode & PaddingConstraints ? 1024 : 0);
243  m_fileInfo.setMaxPadding(m_mode & PaddingConstraints ? (4096 + 1024) : numeric_limits<size_t>::max());
244  m_fileInfo.setForceTagPosition(false);
245  m_fileInfo.setForceIndexPosition(false);
246 
247  // print test conditions
248  list<string> testConditions;
249  if (m_mode & ForceRewring) {
250  testConditions.emplace_back("forcing rewrite");
251  }
252  if (m_mode & Id3v2AndId3v1) {
253  if (m_mode & RemoveTag) {
254  testConditions.emplace_back("removing tag");
255  } else {
256  testConditions.emplace_back("ID3v1 and ID3v2");
257  }
258  } else if (m_mode & Id3v1Only) {
259  testConditions.emplace_back("ID3v1 only");
260  } else {
261  testConditions.emplace_back("ID3v2 only");
262  }
263  if (m_mode & PaddingConstraints) {
264  testConditions.emplace_back("padding constraints");
265  }
266  if (m_mode & UseId3v24) {
267  testConditions.emplace_back("use ID3v2.4");
268  }
269  cerr << endl << "MP3 maker - testmode " << m_mode << ": " << joinStrings(testConditions, ", ") << endl;
270 
271  // do actual tests
272  m_tagStatus = (m_mode & RemoveTag) ? TagStatus::Removed : TagStatus::TestMetaDataPresent;
273  void (OverallTests::*modifyRoutine)(void) = (m_mode & RemoveTag) ? &OverallTests::removeAllTags : &OverallTests::setMp3TestMetaData;
274  makeFile(TestUtilities::workingCopyPath("mtx-test-data/mp3/id3-tag-and-xing-header.mp3"), modifyRoutine, &OverallTests::checkMp3Testfile1);
275  }
276 }
277 #endif
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:535
The Tag class is used to store, read and write tag information.
Definition: tag.h:95
const TagValue & value(const IdentifierType &id) const
Returns the value of the field with the specified id.
Id3v1Tag * id3v1Tag() const
Returns a pointer to the assigned ID3v1 tag or nullptr if none is assigned.
TagTextEncoding descriptionEncoding() const
Returns the description encoding.
Definition: tagvalue.h:545
const std::string & description() const
Returns the description.
Definition: tagvalue.h:443
constexpr int32 position() const
Returns the element position of the current instance.
Definition: positioninset.h:76
const std::vector< std::unique_ptr< Id3v2Tag > > & id3v2Tags() const
Returns pointers to the assigned ID3v2 tags.
ContainerFormat containerFormat() const
Returns the container format of the current file.
DiagLevel level() const
Returns the worst diag level present in the container.
Definition: diagnostics.cpp:53
The OverallTests class tests reading and writing tags and parsing technical information for all suppo...
Definition: overall.h:42
void tags(std::vector< Tag *> &tags) const
Stores all tags assigned to the current file in the specified vector.
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation...
Definition: tagvalue.cpp:253
void setForceFullParse(bool forceFullParse)
Sets whether forcing a full parse is enabled.
void testMp3Parsing()
Tests the MP3 parser via MediaFileInfo.
Definition: overallmp3.cpp:210
Implementation of TagParser::Tag for ID3v2 tags.
Definition: id3v2tag.h:61
Implementation of TagParser::Tag for ID3v1 tags.
Definition: id3v1tag.h:10
std::string toString(TagTextEncoding encoding=TagTextEncoding::Unspecified) const
Converts the value of the current TagValue object to its equivalent std::string representation.
Definition: tagvalue.h:360
std::vector< AbstractTrack * > tracks() const
Returns the tracks for the current file.
The TagValue class wraps values of different types.
Definition: tagvalue.h:64
void setVersion(byte majorVersion, byte revisionVersion)
Sets the version to the specified majorVersion and the specified revisionVersion. ...
Definition: id3v2tag.cpp:383
const TagValue & value(KnownField value) const override
Returns the value of the specified field.
Definition: id3v1tag.cpp:136