Tag Parser  6.2.2
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
overallmp4.cpp
Go to the documentation of this file.
1 #include "./helper.h"
2 #include "./overall.h"
3 
4 #include "../abstracttrack.h"
5 #include "../mp4/mp4ids.h"
6 #include "../mp4/mp4tag.h"
7 
8 namespace Mp4TestFlags {
9 enum TestFlag
10 {
11  ForceRewring = 0x1,
12  KeepTagPos = 0x2,
16  ForceTagPos = 0x8,
17 };
18 }
19 
23 void OverallTests::checkMp4Testfile1()
24 {
25  CPPUNIT_ASSERT(m_fileInfo.containerFormat() == ContainerFormat::Mp4);
26  const auto tracks = m_fileInfo.tracks();
27  CPPUNIT_ASSERT(tracks.size() == 1);
28  for(const auto &track : tracks) {
29  switch(track->id()) {
30  case 1:
31  CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
32  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Aac);
33  CPPUNIT_ASSERT(track->creationTime().year() == 2012);
34  CPPUNIT_ASSERT(track->samplingFrequency() == 44100);
35  CPPUNIT_ASSERT(track->channelConfig() == Mpeg4ChannelConfigs::FrontLeftFrontRight);
36  break;
37  default:
38  CPPUNIT_FAIL("unknown track ID");
39  }
40  }
41  const auto tags = m_fileInfo.tags();
42  switch(m_tagStatus) {
44  CPPUNIT_ASSERT(tags.size() == 1);
45  CPPUNIT_ASSERT(tags.front()->value(KnownField::Title).toString() == "Danse Macabre, Op.40");
46  CPPUNIT_ASSERT(tags.front()->value(KnownField::Artist).toString() == "Saint-SaĆ«ns");
47  CPPUNIT_ASSERT(tags.front()->value(KnownField::Genre).toString() == "Classical");
48  CPPUNIT_ASSERT(tags.front()->value(KnownField::Encoder).toString() == "qaac 1.32, CoreAudioToolbox 7.9.7.3, AAC-LC Encoder, TVBR q63, Quality 96");
49  CPPUNIT_ASSERT(tags.front()->value(KnownField::TrackPosition).toPositionInSet().position() == 10);
50  break;
52  checkMp4TestMetaData();
53  break;
54  case TagStatus::Removed:
55  CPPUNIT_ASSERT(tags.size() == 0);
56  }
57 }
58 
62 void OverallTests::checkMp4Testfile2()
63 {
64  CPPUNIT_ASSERT(m_fileInfo.containerFormat() == ContainerFormat::Mp4);
65  const auto tracks = m_fileInfo.tracks();
66  CPPUNIT_ASSERT(tracks.size() == 5);
67  for(const auto &track : tracks) {
68  switch(track->id()) {
69  case 1:
70  CPPUNIT_ASSERT(track->mediaType() == MediaType::Video);
71  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Avc);
72  CPPUNIT_ASSERT(track->format().sub == SubFormats::AvcHighProfile);
73  CPPUNIT_ASSERT(track->version() == 4);
74  CPPUNIT_ASSERT(track->creationTime().year() == 2013);
75  CPPUNIT_ASSERT(track->pixelSize() == Size(1920, 750));
76  break;
77  case 2:
78  CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
79  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Aac);
80  CPPUNIT_ASSERT(track->format().sub == SubFormats::AacMpeg4LowComplexityProfile);
81  CPPUNIT_ASSERT(!(track->format().extension & ExtensionFormats::SpectralBandReplication));
82  CPPUNIT_ASSERT(!(track->format().extension & ExtensionFormats::ParametricStereo));
83  CPPUNIT_ASSERT(track->language() == "eng");
84  CPPUNIT_ASSERT(track->creationTime().year() == 2013);
85  CPPUNIT_ASSERT(track->samplingFrequency() == 48000);
86  CPPUNIT_ASSERT(track->channelConfig() == Mpeg4ChannelConfigs::FrontLeftFrontRight);
87  break;
88  case 3:
89  CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
90  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Ac3);
91  CPPUNIT_ASSERT(track->language() == "eng");
92  CPPUNIT_ASSERT(track->creationTime().year() == 2013);
93  break;
94  case 4:
95  CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
96  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::DtsHd);
97  CPPUNIT_ASSERT(track->language() == "eng");
98  CPPUNIT_ASSERT(track->creationTime().year() == 2013);
99  break;
100  case 6:
101  CPPUNIT_ASSERT(track->mediaType() == MediaType::Text);
102  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::TimedText);
103  CPPUNIT_ASSERT(track->creationTime().year() == 2013);
104  break;
105  default:
106  CPPUNIT_FAIL("unknown track ID");
107  }
108  }
109  const auto tags = m_fileInfo.tags();
110  switch(m_tagStatus) {
111  case TagStatus::Original:
112  CPPUNIT_ASSERT(tags.size() == 0);
113  break;
115  checkMp4TestMetaData();
116  break;
117  case TagStatus::Removed:
118  CPPUNIT_ASSERT(tags.size() == 0);
119  }
120 }
121 
125 void OverallTests::checkMp4Testfile3()
126 {
127  CPPUNIT_ASSERT(m_fileInfo.containerFormat() == ContainerFormat::Mp4);
128  CPPUNIT_ASSERT(m_fileInfo.container() && m_fileInfo.container()->documentType() == "dash");
129  const auto tracks = m_fileInfo.tracks();
130  CPPUNIT_ASSERT(tracks.size() == 1);
131  for(const auto &track : tracks) {
132  switch(track->id()) {
133  case 1:
134  CPPUNIT_ASSERT(track->mediaType() == MediaType::Video);
135  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Avc);
136  CPPUNIT_ASSERT(track->format().sub == SubFormats::AvcMainProfile);
137  CPPUNIT_ASSERT(track->version() == 3.1);
138  CPPUNIT_ASSERT(track->creationTime().year() == 2014);
139  CPPUNIT_ASSERT(track->pixelSize() == Size(854, 480));
140  CPPUNIT_ASSERT(!strcmp(track->chromaFormat(), "YUV 4:2:0"));
141  break;
142  default:
143  CPPUNIT_FAIL("unknown track ID");
144  }
145  }
146  const auto tags = m_fileInfo.tags();
147  switch(m_tagStatus) {
148  case TagStatus::Original:
149  CPPUNIT_ASSERT(tags.size() == 0);
150  break;
152  checkMp4TestMetaData();
153  break;
154  case TagStatus::Removed:
155  CPPUNIT_ASSERT(tags.size() == 0);
156  }
157 }
158 
162 void OverallTests::checkMp4Testfile4()
163 {
164  CPPUNIT_ASSERT(m_fileInfo.containerFormat() == ContainerFormat::Mp4);
165  CPPUNIT_ASSERT(m_fileInfo.container() && m_fileInfo.container()->documentType() == "M4A ");
166  const auto tracks = m_fileInfo.tracks();
167  CPPUNIT_ASSERT(tracks.size() == 1);
168  for(const auto &track : tracks) {
169  switch(track->id()) {
170  case 1:
171  CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
172  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Alac);
173  CPPUNIT_ASSERT(track->creationTime().year() == 2008);
174  CPPUNIT_ASSERT(track->channelCount() == 2);
175  CPPUNIT_ASSERT(track->bitsPerSample() == 16);
176  break;
177  default:
178  CPPUNIT_FAIL("unknown track ID");
179  }
180  }
181  const auto tags = m_fileInfo.tags();
182  switch(m_tagStatus) {
183  case TagStatus::Original:
184  CPPUNIT_ASSERT(tags.size() == 1);
185  CPPUNIT_ASSERT(tags.front()->value(KnownField::Title).toString() == "Sad Song");
186  CPPUNIT_ASSERT(tags.front()->value(KnownField::Artist).toString() == "Oasis");
187  CPPUNIT_ASSERT(tags.front()->value(KnownField::Album).toString() == "Don't Go Away (Apple Lossless)");
188  CPPUNIT_ASSERT(tags.front()->value(KnownField::Genre).toString() == "Alternative & Punk");
189  CPPUNIT_ASSERT(tags.front()->value(KnownField::Encoder).toString() == "iTunes v7.5.0.20");
190  CPPUNIT_ASSERT(tags.front()->value(KnownField::Year).toString() == "1998");
191  CPPUNIT_ASSERT(tags.front()->value(KnownField::Comment).isEmpty());
192  CPPUNIT_ASSERT(tags.front()->value(KnownField::Cover).dataSize() == 0x58f3);
193  CPPUNIT_ASSERT(BE::toUInt64(tags.front()->value(KnownField::Cover).dataPointer()) == 0xFFD8FFE000104A46);
194  CPPUNIT_ASSERT(tags.front()->value(KnownField::TrackPosition).toPositionInSet() == PositionInSet(3, 4));
195  CPPUNIT_ASSERT(tags.front()->value(KnownField::DiskPosition).toPositionInSet() == PositionInSet(1, 1));
196  break;
198  checkMp4TestMetaData();
199  break;
200  case TagStatus::Removed:
201  CPPUNIT_ASSERT(tags.size() == 0);
202  }
203 }
204 
208 void OverallTests::checkMp4Testfile5()
209 {
210  CPPUNIT_ASSERT(m_fileInfo.containerFormat() == ContainerFormat::Mp4);
211  CPPUNIT_ASSERT(m_fileInfo.container() && m_fileInfo.container()->documentType() == "mp42");
212  const auto tracks = m_fileInfo.tracks();
213  CPPUNIT_ASSERT(tracks.size() == 1);
214  for(const auto &track : tracks) {
215  switch(track->id()) {
216  case 1:
217  CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
218  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Aac);
219  CPPUNIT_ASSERT(track->format().sub == SubFormats::AacMpeg4LowComplexityProfile);
220  CPPUNIT_ASSERT(track->format().extension & ExtensionFormats::SpectralBandReplication);
221  CPPUNIT_ASSERT(track->format().extension & ExtensionFormats::ParametricStereo);
222  CPPUNIT_ASSERT(track->creationTime().year() == 2014);
223  CPPUNIT_ASSERT(track->channelCount() == 2);
224  CPPUNIT_ASSERT(track->channelConfig() == Mpeg4ChannelConfigs::FrontCenter);
225  CPPUNIT_ASSERT(track->extensionChannelConfig() == Mpeg4ChannelConfigs::FrontLeftFrontRight);
226  CPPUNIT_ASSERT(track->samplingFrequency() == 24000);
227  CPPUNIT_ASSERT(track->extensionSamplingFrequency() == 48000);
228  CPPUNIT_ASSERT(track->bitsPerSample() == 16);
229  break;
230  default:
231  CPPUNIT_FAIL("unknown track ID");
232  }
233  }
234  const auto tags = m_fileInfo.tags();
235  switch(m_tagStatus) {
236  case TagStatus::Original:
237  CPPUNIT_ASSERT(tags.size() == 0);
238  break;
240  checkMp4TestMetaData();
241  break;
242  case TagStatus::Removed:
243  CPPUNIT_ASSERT(tags.size() == 0);
244  }
245 }
246 
250 void OverallTests::checkMp4TestMetaData()
251 {
252  // check whether a tag is assigned
253  const auto tags = m_fileInfo.tags();
254  Mp4Tag *tag = m_fileInfo.mp4Tag();
255  CPPUNIT_ASSERT(tags.size() == 1);
256  CPPUNIT_ASSERT(tag != nullptr);
257 
258  // check test meta data
259  CPPUNIT_ASSERT_EQUAL(m_testTitle, tag->value(KnownField::Title));
260  CPPUNIT_ASSERT_EQUAL(m_testComment.toString(), tag->value(KnownField::Comment).toString()); // loss of description is ok
261  CPPUNIT_ASSERT_EQUAL(m_testAlbum, tag->value(KnownField::Album));
262  CPPUNIT_ASSERT_EQUAL(m_preservedMetaData.front(), tag->value(KnownField::Artist));
263  CPPUNIT_ASSERT_EQUAL(m_testPosition, tag->value(KnownField::TrackPosition));
264  CPPUNIT_ASSERT_EQUAL(m_testPosition, tag->value(KnownField::DiskPosition));
265 
266  // TODO: check more fields
267  m_preservedMetaData.pop();
268 }
269 
273 void OverallTests::checkMp4Constraints()
274 {
275  using namespace Mp4TestFlags;
276 
277  CPPUNIT_ASSERT(m_fileInfo.container());
278  if(m_mode & PaddingConstraints) {
279  if(m_mode & ForceRewring) {
280  CPPUNIT_ASSERT_EQUAL(4096ul, m_fileInfo.paddingSize());
281  } else {
282  CPPUNIT_ASSERT(m_fileInfo.paddingSize() >= 1024);
283  CPPUNIT_ASSERT(m_fileInfo.paddingSize() <= (4096 + 1024));
284  }
285  if(!(m_mode & RemoveTag) && (m_fileInfo.container()->documentType() != "dash") && ((m_mode & ForceRewring) || (m_mode & ForceTagPos))) {
286  CPPUNIT_ASSERT_EQUAL(m_expectedTagPos, m_fileInfo.container()->determineTagPosition());
287  }
288  }
289 }
290 
291 void OverallTests::setMp4TestMetaData()
292 {
293  // ensure a tag exists
294  Tag *tag = m_fileInfo.container()->createTag();
295 
296  // assign test meta data
297  tag->setValue(KnownField::Title, m_testTitle);
298  tag->setValue(KnownField::Comment, m_testComment);
299  tag->setValue(KnownField::Album, m_testAlbum);
300  m_preservedMetaData.push(tag->value(KnownField::Artist));
301  tag->setValue(KnownField::TrackPosition, m_testPosition);
302  tag->setValue(KnownField::DiskPosition, m_testPosition);
303  // TODO: set more fields
304 }
305 
310 {
311  cerr << endl << "MP4 parser" << endl;
312  m_fileInfo.setForceFullParse(false);
313  m_tagStatus = TagStatus::Original;
314  parseFile(TestUtilities::testFilePath("mtx-test-data/mp4/10-DanseMacabreOp.40.m4a"), &OverallTests::checkMp4Testfile1);
315  parseFile(TestUtilities::testFilePath("mtx-test-data/mp4/1080p-DTS-HD-7.1.mp4"), &OverallTests::checkMp4Testfile2);
316  parseFile(TestUtilities::testFilePath("mtx-test-data/mp4/dash/dragon-age-inquisition-H1LkM6IVlm4-video.mp4"), &OverallTests::checkMp4Testfile3);
317  parseFile(TestUtilities::testFilePath("mtx-test-data/alac/othertest-itunes.m4a"), &OverallTests::checkMp4Testfile4);
318  parseFile(TestUtilities::testFilePath("mtx-test-data/aac/he-aacv2-ps.m4a"), &OverallTests::checkMp4Testfile5);
319 }
320 
321 #ifdef PLATFORM_UNIX
322 
326 void OverallTests::testMp4Making()
327 {
328  // full parse is required to determine padding
329  m_fileInfo.setForceFullParse(true);
330 
331  // do the test under different conditions
332  for(m_mode = 0; m_mode != 0x20; ++m_mode) {
333  using namespace Mp4TestFlags;
334 
335  // setup test conditions
336 
337  m_fileInfo.setForceRewrite(m_mode & ForceRewring);
338  if(m_mode & KeepTagPos) {
339  m_fileInfo.setTagPosition(ElementPosition::Keep);
340  } else {
341  m_fileInfo.setTagPosition(m_mode & TagsBeforeData ? ElementPosition::BeforeData : ElementPosition::AfterData);
342  }
343  m_fileInfo.setIndexPosition(m_fileInfo.tagPosition());
344  m_fileInfo.setPreferredPadding(m_mode & PaddingConstraints ? 4096 : 0);
345  m_fileInfo.setMinPadding(m_mode & PaddingConstraints ? 1024 : 0);
346  m_fileInfo.setMaxPadding(m_mode & PaddingConstraints ? (4096 + 1024) : static_cast<size_t>(-1));
347  m_fileInfo.setForceTagPosition(m_mode & ForceTagPos);
348  m_fileInfo.setForceIndexPosition(m_mode & ForceTagPos);
349 
350  // print test conditions
351  list<string> testConditions;
352  if(m_mode & ForceRewring) {
353  testConditions.emplace_back("forcing rewrite");
354  }
355  if(m_mode & KeepTagPos) {
356  if(m_mode & RemoveTag) {
357  testConditions.emplace_back("removing tag");
358  } else {
359  testConditions.emplace_back("keeping tag position");
360  }
361  } else if(m_mode & TagsBeforeData) {
362  testConditions.emplace_back("tags before data");
363  } else {
364  testConditions.emplace_back("tags after data");
365  }
366  if(m_mode & PaddingConstraints) {
367  testConditions.emplace_back("padding constraints");
368  }
369  if(m_mode & ForceTagPos) {
370  testConditions.emplace_back("forcing tag position");
371  }
372  cerr << endl << "MP4 maker - testmode " << m_mode << ": " << joinStrings(testConditions, ", ") << endl;
373 
374  // do actual tests
375  m_tagStatus = (m_mode & RemoveTag) ? TagStatus::Removed : TagStatus::TestMetaDataPresent;
376  void (OverallTests::*modifyRoutine)(void) = (m_mode & RemoveTag) ? &OverallTests::removeAllTags : &OverallTests::setMp4TestMetaData;
377  makeFile(TestUtilities::workingCopyPath("mtx-test-data/mp4/10-DanseMacabreOp.40.m4a"), modifyRoutine, &OverallTests::checkMp4Testfile1);
378  makeFile(TestUtilities::workingCopyPath("mtx-test-data/mp4/1080p-DTS-HD-7.1.mp4"), modifyRoutine, &OverallTests::checkMp4Testfile2);
379  makeFile(TestUtilities::workingCopyPath("mtx-test-data/mp4/dash/dragon-age-inquisition-H1LkM6IVlm4-video.mp4"), modifyRoutine, &OverallTests::checkMp4Testfile3);
380  makeFile(TestUtilities::workingCopyPath("mtx-test-data/alac/othertest-itunes.m4a"), modifyRoutine, &OverallTests::checkMp4Testfile4);
381  makeFile(TestUtilities::workingCopyPath("mtx-test-data/aac/he-aacv2-ps.m4a"), modifyRoutine, &OverallTests::checkMp4Testfile5);
382  }
383 }
384 #endif
The Size class defines the size of a two-dimensional object using integer point precision.
Definition: size.h:16
The PositionInSet class describes the position of an element in a set which consists of a certain num...
Definition: positioninset.h:20
Implementation of Media::Tag for the MP4 container.
Definition: mp4tag.h:90
The OverallTests class tests reading and writing tags and parsing technical information for all suppo...
Definition: overall.h:42
The Tag class is used to store, read and write tag information.
Definition: tag.h:98
const TagValue & value(KnownField value) const
Returns the value of the specified field.
Definition: mp4tag.cpp:58
void testMp4Parsing()
Tests the MP4 parser via MediaFileInfo.
Definition: overallmp4.cpp:309
virtual bool setValue(KnownField field, const TagValue &value)=0
Assigns the given value to the specified field.
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:316
virtual const TagValue & value(KnownField field) const =0
Returns the value of the specified field.