Tag Parser  6.5.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 "../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 tags(std::vector< Tag *> &tags) const
Stores all tags assigned to the current file in the specified vector.
const std::vector< std::unique_ptr< Id3v2Tag > > & id3v2Tags() const
Returns pointers to the assigned ID3v2 tags.
Id3v1Tag * id3v1Tag() const
Returns a pointer to the assigned ID3v1 tag or nullptr if none is assigned.
NotificationType worstNotificationTypeIncludingRelatedObjects() const
Returns the worst notification type including related objects such as track, tag and container...
void setVersion(byte majorVersion, byte revisionVersion)
Sets the version to the specified majorVersion and the specified revisionVersion. ...
Definition: id3v2tag.cpp:312
std::vector< AbstractTrack * > tracks() const
Returns the tracks for the current file.
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
void setForceFullParse(bool forceFullParse)
Sets whether forcing a full parse is enabled.
const std::string & description() const
Returns the description.
Definition: tagvalue.h:387
ContainerFormat containerFormat() const
Returns the container format of the current file.
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