Tag Parser  6.5.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  CPPUNIT_ASSERT(m_fileInfo.worstNotificationTypeIncludingRelatedObjects() <= NotificationType::Information);
84 }
85 
89 void OverallTests::checkMp3TestMetaData()
90 {
91  using namespace Mp3TestFlags;
92 
93  // check whether tags are assigned according to the current test mode
94  Id3v1Tag *id3v1Tag = nullptr;
95  Id3v2Tag *id3v2Tag = nullptr;
96  if(m_mode & Id3v2AndId3v1) {
97  CPPUNIT_ASSERT(id3v1Tag = m_fileInfo.id3v1Tag());
98  CPPUNIT_ASSERT(id3v2Tag = m_fileInfo.id3v2Tags().at(0).get());
99  } else if(m_mode & Id3v1Only) {
100  CPPUNIT_ASSERT(id3v1Tag = m_fileInfo.id3v1Tag());
101  CPPUNIT_ASSERT(m_fileInfo.id3v2Tags().empty());
102  } else {
103  CPPUNIT_ASSERT(!m_fileInfo.id3v1Tag());
104  CPPUNIT_ASSERT(id3v2Tag = m_fileInfo.id3v2Tags().at(0).get());
105  }
106 
107  // check common test meta data
108  if(id3v1Tag) {
109  CPPUNIT_ASSERT_EQUAL(m_testTitle, id3v1Tag->value(KnownField::Title));
110  CPPUNIT_ASSERT_EQUAL(m_testComment.toString(), id3v1Tag->value(KnownField::Comment).toString()); // ignore encoding here
111  CPPUNIT_ASSERT_EQUAL(m_testAlbum, id3v1Tag->value(KnownField::Album));
112  CPPUNIT_ASSERT_EQUAL(m_preservedMetaData.front(), id3v1Tag->value(KnownField::Artist));
113  m_preservedMetaData.pop();
114  }
115  if(id3v2Tag) {
116  const TagValue &titleValue = id3v2Tag->value(KnownField::Title);
117  const TagValue &commentValue = id3v2Tag->value(KnownField::Comment);
118 
119  if(m_mode & UseId3v24) {
120  CPPUNIT_ASSERT_EQUAL(m_testTitle, titleValue);
121  CPPUNIT_ASSERT_EQUAL(m_testComment, commentValue);
122  CPPUNIT_ASSERT_EQUAL(m_testAlbum, id3v2Tag->value(KnownField::Album));
123  CPPUNIT_ASSERT_EQUAL(m_preservedMetaData.front(), id3v2Tag->value(KnownField::Artist));
124  // TODO: check more fields
125  } else {
126  CPPUNIT_ASSERT_MESSAGE("not attempted to use UTF-8 in ID3v2.3", titleValue.dataEncoding() == TagTextEncoding::Utf16LittleEndian);
127  CPPUNIT_ASSERT_EQUAL(m_testTitle.toString(), titleValue.toString(TagTextEncoding::Utf8));
128  CPPUNIT_ASSERT_MESSAGE("not attempted to use UTF-8 in ID3v2.3", commentValue.dataEncoding() == TagTextEncoding::Utf16LittleEndian);
129  CPPUNIT_ASSERT_MESSAGE("not attempted to use UTF-8 in ID3v2.3", commentValue.descriptionEncoding() == TagTextEncoding::Utf16LittleEndian);
130  CPPUNIT_ASSERT_EQUAL(m_testComment.toString(), commentValue.toString(TagTextEncoding::Utf8));
131  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());
132  CPPUNIT_ASSERT_EQUAL(m_testAlbum.toString(TagTextEncoding::Utf8), id3v2Tag->value(KnownField::Album).toString(TagTextEncoding::Utf8));
133  CPPUNIT_ASSERT_EQUAL(m_preservedMetaData.front(), id3v2Tag->value(KnownField::Artist));
134  // TODO: check more fields
135  }
136 
137  m_preservedMetaData.pop();
138  }
139 
140  // test ID3v1 specific test meta data
141  if(id3v1Tag) {
142  CPPUNIT_ASSERT(id3v1Tag->value(KnownField::TrackPosition).toPositionInSet().position() == m_testPosition.toPositionInSet().position());
143  }
144  // test ID3v2 specific test meta data
145  if(id3v2Tag) {
146  CPPUNIT_ASSERT(id3v2Tag->value(KnownField::TrackPosition) == m_testPosition);
147  CPPUNIT_ASSERT(id3v2Tag->value(KnownField::DiskPosition) == m_testPosition);
148  }
149 }
150 
154 void OverallTests::checkMp3PaddingConstraints()
155 {
156  using namespace Mp3TestFlags;
157 
158  if(!(m_mode & Id3v1Only)) {
159  if(m_mode & PaddingConstraints) {
160  if(m_mode & ForceRewring) {
161  CPPUNIT_ASSERT(m_fileInfo.paddingSize() == 4096);
162  } else {
163  CPPUNIT_ASSERT(m_fileInfo.paddingSize() >= 1024);
164  CPPUNIT_ASSERT(m_fileInfo.paddingSize() <= (4096 + 1024));
165  }
166  }
167  } else {
168  // adding padding is not possible if no ID3v2 tag is present
169  }
170  // TODO: check rewriting behaviour
171 }
172 
173 void OverallTests::setMp3TestMetaData()
174 {
175  using namespace Mp3TestFlags;
176 
177  // ensure tags are assigned according to the current test mode
178  Id3v1Tag *id3v1Tag = nullptr;
179  Id3v2Tag *id3v2Tag = nullptr;
180  if(m_mode & Id3v2AndId3v1) {
181  id3v1Tag = m_fileInfo.createId3v1Tag();
182  id3v2Tag = m_fileInfo.createId3v2Tag();
183  } else if(m_mode & Id3v1Only) {
184  id3v1Tag = m_fileInfo.createId3v1Tag();
185  m_fileInfo.removeAllId3v2Tags();
186  } else {
187  m_fileInfo.removeId3v1Tag();
188  id3v2Tag = m_fileInfo.createId3v2Tag();
189  }
190  if(!(m_mode & Id3v1Only) && m_mode & UseId3v24) {
191  id3v2Tag->setVersion(4, 0);
192  }
193 
194  // assign some test meta data
195  for(Tag *tag : initializer_list<Tag *>{id3v1Tag, id3v2Tag}) {
196  if(tag) {
197  tag->setValue(KnownField::Title, m_testTitle);
198  tag->setValue(KnownField::Comment, m_testComment);
199  tag->setValue(KnownField::Album, m_testAlbum);
200  m_preservedMetaData.push(tag->value(KnownField::Artist));
201  tag->setValue(KnownField::TrackPosition, m_testPosition);
202  tag->setValue(KnownField::DiskPosition, m_testPosition);
203  // TODO: set more fields
204  }
205  }
206 }
207 
212 {
213  cerr << endl << "MP3 parser" << endl;
214  m_fileInfo.setForceFullParse(false);
215  m_tagStatus = TagStatus::Original;
216  parseFile(TestUtilities::testFilePath("mtx-test-data/mp3/id3-tag-and-xing-header.mp3"), &OverallTests::checkMp3Testfile1);
217 }
218 
219 #ifdef PLATFORM_UNIX
220 
224 void OverallTests::testMp3Making()
225 {
226  // full parse is required to determine padding
227  m_fileInfo.setForceFullParse(true);
228 
229  // do the test under different conditions
230  for(m_mode = 0; m_mode != 0x20; ++m_mode) {
231  using namespace Mp3TestFlags;
232 
233  // setup test conditions
234  m_fileInfo.setForceRewrite(m_mode & ForceRewring);
235  if(m_mode & UseId3v24) {
236  if(m_mode & Id3v1Only) {
237  continue;
238  }
239  }
240  m_fileInfo.setTagPosition(ElementPosition::Keep);
241  m_fileInfo.setIndexPosition(ElementPosition::Keep);
242  m_fileInfo.setPreferredPadding(m_mode & PaddingConstraints ? 4096 : 0);
243  m_fileInfo.setMinPadding(m_mode & PaddingConstraints ? 1024 : 0);
244  m_fileInfo.setMaxPadding(m_mode & PaddingConstraints ? (4096 + 1024) : static_cast<size_t>(-1));
245  m_fileInfo.setForceTagPosition(false);
246  m_fileInfo.setForceIndexPosition(false);
247 
248  // print test conditions
249  list<string> testConditions;
250  if(m_mode & ForceRewring) {
251  testConditions.emplace_back("forcing rewrite");
252  }
253  if(m_mode & Id3v2AndId3v1) {
254  if(m_mode & RemoveTag) {
255  testConditions.emplace_back("removing tag");
256  } else {
257  testConditions.emplace_back("ID3v1 and ID3v2");
258  }
259  } else if(m_mode & Id3v1Only) {
260  testConditions.emplace_back("ID3v1 only");
261  } else {
262  testConditions.emplace_back("ID3v2 only");
263  }
264  if(m_mode & PaddingConstraints) {
265  testConditions.emplace_back("padding constraints");
266  }
267  if(m_mode & UseId3v24) {
268  testConditions.emplace_back("use ID3v2.4");
269  }
270  cerr << endl << "MP3 maker - testmode " << m_mode << ": " << joinStrings(testConditions, ", ") << endl;
271 
272  // do actual tests
273  m_tagStatus = (m_mode & RemoveTag) ? TagStatus::Removed : TagStatus::TestMetaDataPresent;
274  void (OverallTests::*modifyRoutine)(void) = (m_mode & RemoveTag) ? &OverallTests::removeAllTags : &OverallTests::setMp3TestMetaData;
275  makeFile(TestUtilities::workingCopyPath("mtx-test-data/mp3/id3-tag-and-xing-header.mp3"), modifyRoutine, &OverallTests::checkMp3Testfile1);
276  }
277 }
278 #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:211
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