Tag Parser  7.0.1
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:518
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:528
const std::string & description() const
Returns the description.
Definition: tagvalue.h:426
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
Definition: diagnostics.cpp:32
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:225
void setForceFullParse(bool forceFullParse)
Sets whether forcing a full parse is enabled.
void testMp3Parsing()
Tests the MP3 parser via MediaFileInfo.
Definition: overallmp3.cpp:210
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:343
std::vector< AbstractTrack * > tracks() const
Returns the tracks for the current file.
void setVersion(byte majorVersion, byte revisionVersion)
Sets the version to the specified majorVersion and the specified revisionVersion. ...
Definition: id3v2tag.cpp:381
const TagValue & value(KnownField value) const override
Returns the value of the specified field.
Definition: id3v1tag.cpp:122