Tag Parser  6.4.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 "../mpegaudio/mpegaudioframe.h"
6 #include "../id3/id3v1tag.h"
7 #include "../id3/id3v2tag.h"
8 
9 namespace Mp3TestFlags {
11 {
12  ForceRewring = 0x1,
15  Id3v1Only = 0x8,
17  UseId3v24 = 0x10,
18 };
19 }
20 
24 void OverallTests::checkMp3Testfile1()
25 {
26  CPPUNIT_ASSERT(m_fileInfo.containerFormat() == ContainerFormat::MpegAudioFrames);
27  const auto tracks = m_fileInfo.tracks();
28  CPPUNIT_ASSERT(tracks.size() == 1);
29  for(const auto &track : tracks) {
30  CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
31  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Mpeg1Audio);
32  CPPUNIT_ASSERT(track->format().sub == SubFormats::Mpeg1Layer3);
33  CPPUNIT_ASSERT(track->channelCount() == 2);
34  CPPUNIT_ASSERT(track->channelConfig() == static_cast<byte>(MpegChannelMode::JointStereo));
35  CPPUNIT_ASSERT(track->samplingFrequency() == 44100);
36  CPPUNIT_ASSERT(track->duration().seconds() == 3);
37  }
38  const auto tags = m_fileInfo.tags();
39  switch(m_tagStatus) {
41  CPPUNIT_ASSERT(m_fileInfo.id3v1Tag());
42  CPPUNIT_ASSERT(m_fileInfo.id3v2Tags().size() == 1);
43  CPPUNIT_ASSERT(tags.size() == 2);
44  for(const auto &tag : tags) {
45  CPPUNIT_ASSERT(tag->value(KnownField::TrackPosition).toPositionInSet().position() == 4);
46  CPPUNIT_ASSERT(tag->value(KnownField::Year).toString() == "1984");
47  switch(tag->type()) {
48  case TagType::Id3v1Tag:
49  CPPUNIT_ASSERT(tag->value(KnownField::Title).toString() == "Cohesion");
50  CPPUNIT_ASSERT(tag->value(KnownField::Artist).toString() == "Minutemen");
51  CPPUNIT_ASSERT(tag->value(KnownField::Album).toString() == "Double Nickels On The Dime");
52  CPPUNIT_ASSERT(tag->value(KnownField::Genre).toString() == "Punk Rock");
53  CPPUNIT_ASSERT(tag->value(KnownField::Comment).toString() == "ExactAudioCopy v0.95b4");
54  break;
55  case TagType::Id3v2Tag:
56  CPPUNIT_ASSERT(tag->value(KnownField::Title).dataEncoding() == TagTextEncoding::Utf16LittleEndian);
57  CPPUNIT_ASSERT(tag->value(KnownField::Title).toWString() == u"Cohesion");
58  CPPUNIT_ASSERT(tag->value(KnownField::Title).toString(TagTextEncoding::Utf8) == "Cohesion");
59  CPPUNIT_ASSERT(tag->value(KnownField::Artist).toWString() == u"Minutemen");
60  CPPUNIT_ASSERT(tag->value(KnownField::Artist).toString(TagTextEncoding::Utf8) == "Minutemen");
61  CPPUNIT_ASSERT(tag->value(KnownField::Album).toWString() == u"Double Nickels On The Dime");
62  CPPUNIT_ASSERT(tag->value(KnownField::Album).toString(TagTextEncoding::Utf8) == "Double Nickels On The Dime");
63  CPPUNIT_ASSERT(tag->value(KnownField::Genre).toWString() == u"Punk Rock");
64  CPPUNIT_ASSERT(tag->value(KnownField::Genre).toString(TagTextEncoding::Utf8) == "Punk Rock");
65  CPPUNIT_ASSERT(tag->value(KnownField::Comment).toWString() == u"ExactAudioCopy v0.95b4");
66  CPPUNIT_ASSERT(tag->value(KnownField::Comment).toString(TagTextEncoding::Utf8) == "ExactAudioCopy v0.95b4");
67  CPPUNIT_ASSERT(tag->value(KnownField::TrackPosition).toPositionInSet().total() == 43);
68  CPPUNIT_ASSERT(tag->value(KnownField::Length).toTimeSpan().isNull());
69  CPPUNIT_ASSERT(tag->value(KnownField::Lyricist).isEmpty());
70  break;
71  default:
72  ;
73  }
74  }
75  break;
77  checkMp3TestMetaData();
78  break;
79  case TagStatus::Removed:
80  CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
81  }
82 
83 }
84 
88 void OverallTests::checkMp3TestMetaData()
89 {
90  using namespace Mp3TestFlags;
91 
92  // check whether tags are assigned according to the current test mode
93  Id3v1Tag *id3v1Tag = nullptr;
94  Id3v2Tag *id3v2Tag = nullptr;
95  if(m_mode & Id3v2AndId3v1) {
96  CPPUNIT_ASSERT(id3v1Tag = m_fileInfo.id3v1Tag());
97  CPPUNIT_ASSERT(id3v2Tag = m_fileInfo.id3v2Tags().at(0).get());
98  } else if(m_mode & Id3v1Only) {
99  CPPUNIT_ASSERT(id3v1Tag = m_fileInfo.id3v1Tag());
100  CPPUNIT_ASSERT(m_fileInfo.id3v2Tags().empty());
101  } else {
102  CPPUNIT_ASSERT(!m_fileInfo.id3v1Tag());
103  CPPUNIT_ASSERT(id3v2Tag = m_fileInfo.id3v2Tags().at(0).get());
104  }
105 
106  // check common test meta data
107  if(id3v1Tag) {
108  CPPUNIT_ASSERT_EQUAL(m_testTitle, id3v1Tag->value(KnownField::Title));
109  CPPUNIT_ASSERT_EQUAL(m_testComment.toString(), id3v1Tag->value(KnownField::Comment).toString()); // ignore encoding here
110  CPPUNIT_ASSERT_EQUAL(m_testAlbum, id3v1Tag->value(KnownField::Album));
111  CPPUNIT_ASSERT_EQUAL(m_preservedMetaData.front(), id3v1Tag->value(KnownField::Artist));
112  m_preservedMetaData.pop();
113  }
114  if(id3v2Tag) {
115  const TagValue &titleValue = id3v2Tag->value(KnownField::Title);
116  const TagValue &commentValue = id3v2Tag->value(KnownField::Comment);
117 
118  if(m_mode & UseId3v24) {
119  CPPUNIT_ASSERT_EQUAL(m_testTitle, titleValue);
120  CPPUNIT_ASSERT_EQUAL(m_testComment, commentValue);
121  CPPUNIT_ASSERT_EQUAL(m_testAlbum, id3v2Tag->value(KnownField::Album));
122  CPPUNIT_ASSERT_EQUAL(m_preservedMetaData.front(), id3v2Tag->value(KnownField::Artist));
123  // TODO: check more fields
124  } else {
125  CPPUNIT_ASSERT_MESSAGE("not attempted to use UTF-8 in ID3v2.3", titleValue.dataEncoding() == TagTextEncoding::Utf16LittleEndian);
126  CPPUNIT_ASSERT_EQUAL(m_testTitle.toString(), titleValue.toString(TagTextEncoding::Utf8));
127  CPPUNIT_ASSERT_MESSAGE("not attempted to use UTF-8 in ID3v2.3", commentValue.dataEncoding() == TagTextEncoding::Utf16LittleEndian);
128  CPPUNIT_ASSERT_MESSAGE("not attempted to use UTF-8 in ID3v2.3", commentValue.descriptionEncoding() == TagTextEncoding::Utf16LittleEndian);
129  CPPUNIT_ASSERT_EQUAL(m_testComment.toString(), commentValue.toString(TagTextEncoding::Utf8));
130  CPPUNIT_ASSERT_EQUAL_MESSAGE("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(id3v1Tag->value(KnownField::TrackPosition).toPositionInSet().position() == m_testPosition.toPositionInSet().position());
142  }
143  // test ID3v2 specific test meta data
144  if(id3v2Tag) {
145  CPPUNIT_ASSERT(id3v2Tag->value(KnownField::TrackPosition) == m_testPosition);
146  CPPUNIT_ASSERT(id3v2Tag->value(KnownField::DiskPosition) == m_testPosition);
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(m_fileInfo.paddingSize() == 4096);
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) : static_cast<size_t>(-1));
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
The TagValue class wraps values of different types.
Definition: tagvalue.h:64
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:479
void setVersion(byte majorVersion, byte revisionVersion)
Sets the version to the specified majorVersion and the specified revisionVersion. ...
Definition: id3v2tag.cpp:312
TagTextEncoding descriptionEncoding() const
Returns the description encoding.
Definition: tagvalue.h:489
The OverallTests class tests reading and writing tags and parsing technical information for all suppo...
Definition: overall.h:44
The Tag class is used to store, read and write tag information.
Definition: tag.h:98
Implementation of Media::Tag for ID3v2 tags.
Definition: id3v2tag.h:55
Implementation of Media::Tag for ID3v1 tags.
Definition: id3v1tag.h:9
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation...
Definition: tagvalue.cpp:243
void testMp3Parsing()
Tests the MP3 parser via MediaFileInfo.
Definition: overallmp3.cpp:210
const TagValue & value(const typename Id3v2Frame::identifierType &id) const
Definition: id3v2tag.cpp:150
constexpr int32 position() const
Returns the element position of the current instance.
Definition: positioninset.h:75
const TagValue & value(KnownField value) const
Returns the value of the specified field.
Definition: id3v1tag.cpp:139
const std::string & description() const
Returns the description.
Definition: tagvalue.h:387
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:320