Tag Parser  6.5.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
overallmkv.cpp
Go to the documentation of this file.
1 #include <c++utilities/chrono/format.h>
2 
3 #include "./overall.h"
4 
5 #include "../abstracttrack.h"
6 #include "../mpegaudio/mpegaudioframe.h"
7 #include "../mp4/mp4ids.h"
8 #include "../matroska/matroskacontainer.h"
9 
10 #include <c++utilities/chrono/timespan.h>
11 #include <c++utilities/conversion/binaryconversion.h>
12 #include <c++utilities/conversion/stringconversion.h>
13 #include <c++utilities/io/misc.h>
14 
15 #include <fstream>
16 #include <cstring>
17 
18 using namespace ChronoUtilities;
19 
20 namespace MkvTestFlags {
22 {
23  ForceRewring = 0x1,
24  KeepTagPos = 0x2,
27  KeepIndexPos = 0x4,
30  ForceTagPos = 0x10,
31  ForceIndexPos = 0x20,
32 };
33 }
34 
38 void OverallTests::checkMkvTestfile1()
39 {
40  CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
41  CPPUNIT_ASSERT_EQUAL(TimeSpan::fromMinutes(1) + TimeSpan::fromSeconds(27) + TimeSpan::fromMilliseconds(336), m_fileInfo.duration());
42  const auto tracks = m_fileInfo.tracks();
43  CPPUNIT_ASSERT_EQUAL(2_st, tracks.size());
44  for(const auto &track : tracks) {
45  switch(track->id()) {
46  case 2422994868:
47  CPPUNIT_ASSERT_EQUAL(MediaType::Video, track->mediaType());
48  CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::MicrosoftMpeg4, track->format().general);
49  break;
50  case 3653291187:
51  CPPUNIT_ASSERT_EQUAL(MediaType::Audio, track->mediaType());
52  CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::Mpeg1Audio, track->format().general);
53  CPPUNIT_ASSERT_EQUAL(48000u, track->samplingFrequency());
54  break;
55  default:
56  CPPUNIT_FAIL("unknown track ID");
57  }
58  }
59  const auto tags = m_fileInfo.tags();
60  switch(m_tagStatus) {
62  CPPUNIT_ASSERT(tags.size() == 1);
63  CPPUNIT_ASSERT(tags.front()->value(KnownField::Title).toString() == "Big Buck Bunny - test 1");
64  CPPUNIT_ASSERT(tags.front()->value(KnownField::Artist).isEmpty());
65  CPPUNIT_ASSERT(tags.front()->value(KnownField::Comment).toString() == "Matroska Validation File1, basic MPEG4.2 and MP3 with only SimpleBlock");
66  CPPUNIT_ASSERT(tags.front()->value(KnownField::Year).toString() == "2010");
67  break;
69  checkMkvTestMetaData();
70  break;
71  case TagStatus::Removed:
72  CPPUNIT_ASSERT_EQUAL(0_st, tags.size());
73  }
74  CPPUNIT_ASSERT(m_fileInfo.worstNotificationTypeIncludingRelatedObjects() <= NotificationType::Information);
75 }
76 
80 void OverallTests::checkMkvTestfile2()
81 {
82  CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
83  CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(47) + TimeSpan::fromMilliseconds(509), m_fileInfo.duration());
84  const auto tracks = m_fileInfo.tracks();
85  CPPUNIT_ASSERT_EQUAL(2_st, tracks.size());
86  for(const auto &track : tracks) {
87  switch(track->id()) {
88  case 1863976627:
89  CPPUNIT_ASSERT(track->mediaType() == MediaType::Video);
90  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Avc);
91  CPPUNIT_ASSERT(track->displaySize() == Size(1354, 576));
92  break;
93  case 3134325680:
94  CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
95  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Aac);
96  CPPUNIT_ASSERT(track->samplingFrequency() == 48000);
97  break;
98  default:
99  CPPUNIT_FAIL("unknown track ID");
100  }
101  }
102  const auto tags = m_fileInfo.tags();
103  switch(m_tagStatus) {
104  case TagStatus::Original:
105  CPPUNIT_ASSERT(tags.size() == 1);
106  CPPUNIT_ASSERT(tags.front()->value(KnownField::Title).toString() == "Elephant Dream - test 2");
107  CPPUNIT_ASSERT(tags.front()->value(KnownField::Artist).isEmpty());
108  CPPUNIT_ASSERT(tags.front()->value(KnownField::Comment).toString() == "Matroska Validation File 2, 100,000 timecode scale, odd aspect ratio, and CRC-32. Codecs are AVC and AAC");
109  break;
111  checkMkvTestMetaData();
112  break;
113  case TagStatus::Removed:
114  CPPUNIT_ASSERT_EQUAL(0_st, tags.size());
115  }
116  CPPUNIT_ASSERT(m_fileInfo.worstNotificationTypeIncludingRelatedObjects() <= NotificationType::Information);
117 }
118 
122 void OverallTests::checkMkvTestfile3()
123 {
124  CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
125  CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(49) + TimeSpan::fromMilliseconds(64), m_fileInfo.duration());
126  const auto tracks = m_fileInfo.tracks();
127  CPPUNIT_ASSERT_EQUAL(2_st, tracks.size());
128  for(const auto &track : tracks) {
129  switch(track->id()) {
130  case 3927961528:
131  CPPUNIT_ASSERT(track->mediaType() == MediaType::Video);
132  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Avc);
133  CPPUNIT_ASSERT(track->displaySize() == Size(1024, 576));
134  break;
135  case 3391885737:
136  CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
137  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Mpeg1Audio);
138  CPPUNIT_ASSERT(track->samplingFrequency() == 48000);
139  break;
140  default:
141  CPPUNIT_FAIL("unknown track ID");
142  }
143  }
144  const auto tags = m_fileInfo.tags();
145  switch(m_tagStatus) {
146  case TagStatus::Original:
147  CPPUNIT_ASSERT(tags.size() == 1);
148  CPPUNIT_ASSERT(tags.front()->value(KnownField::Title).toString() == "Elephant Dream - test 3");
149  CPPUNIT_ASSERT(tags.front()->value(KnownField::Artist).isEmpty());
150  CPPUNIT_ASSERT(tags.front()->value(KnownField::Comment).toString() == "Matroska Validation File 3, header stripping on the video track and no SimpleBlock");
151  break;
153  checkMkvTestMetaData();
154  break;
155  case TagStatus::Removed:
156  CPPUNIT_ASSERT_EQUAL(0_st, tags.size());
157  }
158  CPPUNIT_ASSERT(m_fileInfo.worstNotificationTypeIncludingRelatedObjects() <= NotificationType::Information);
159 }
160 
166 void OverallTests::checkMkvTestfile4()
167 {
168  CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
169  CPPUNIT_ASSERT_EQUAL(TimeSpan(), m_fileInfo.duration());
170  // this file is messed up, it should contain tags but it doesn't
171  const auto tracks = m_fileInfo.tracks();
172  CPPUNIT_ASSERT_EQUAL(2_st, tracks.size());
173  for(const auto &track : tracks) {
174  switch(track->id()) {
175  case 1368622492:
176  CPPUNIT_ASSERT(track->mediaType() == MediaType::Video);
177  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Theora);
178  CPPUNIT_ASSERT(track->displaySize() == Size(1280, 720));
179  break;
180  case 3171450505:
181  CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
182  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Vorbis);
183  CPPUNIT_ASSERT(track->samplingFrequency() == 48000);
184  CPPUNIT_ASSERT(track->channelCount() == 2);
185  switch(m_tagStatus) {
186  case TagStatus::Original:
187  case TagStatus::Removed:
188  CPPUNIT_ASSERT_EQUAL("und"s, track->language());
189  CPPUNIT_ASSERT_EQUAL(string(), track->name());
190  CPPUNIT_ASSERT(track->isEnabled());
191  CPPUNIT_ASSERT(!track->isForced());
192  CPPUNIT_ASSERT(!track->isDefault());
193  break;
195  CPPUNIT_ASSERT_EQUAL("ger"s, track->language());
196  CPPUNIT_ASSERT_EQUAL("the name"s, track->name());
197  CPPUNIT_ASSERT(track->isEnabled());
198  CPPUNIT_ASSERT(track->isForced());
199  CPPUNIT_ASSERT(track->isDefault());
200  break;
201  }
202  break;
203  default:
204  CPPUNIT_FAIL("unknown track ID");
205  }
206  }
207  const auto tags = m_fileInfo.tags();
208  switch(m_tagStatus) {
209  case TagStatus::Original:
210  case TagStatus::Removed:
211  CPPUNIT_ASSERT_EQUAL(0_st, tags.size());
212  break;
214  checkMkvTestMetaData();
215  break;
216  }
217 
218  // tolerate critical notifications here because live stream feature used by the file is not supported in v6 yet
219  CPPUNIT_ASSERT(m_fileInfo.worstNotificationTypeIncludingRelatedObjects() <= NotificationType::Critical);
220 }
221 
225 void OverallTests::checkMkvTestfile5()
226 {
227  CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
228  CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(46) + TimeSpan::fromMilliseconds(665), m_fileInfo.duration());
229  const auto tracks = m_fileInfo.tracks();
230  CPPUNIT_ASSERT_EQUAL(11_st, tracks.size());
231  for(const auto &track : tracks) {
232  switch(track->id()) {
233  case 1258329745:
234  CPPUNIT_ASSERT(track->mediaType() == MediaType::Video);
235  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Avc);
236  CPPUNIT_ASSERT(track->displaySize() == Size(1024, 576));
237  break;
238  case 3452711582:
239  CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
240  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Aac);
241  CPPUNIT_ASSERT(track->samplingFrequency() == 48000);
242  CPPUNIT_ASSERT(track->channelConfig() == Mpeg4ChannelConfigs::FrontLeftFrontRight);
243  break;
244  case 3554194305:
245  CPPUNIT_ASSERT(track->mediaType() == MediaType::Text);
246  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::TextSubtitle);
247  CPPUNIT_ASSERT(track->language() == "ger");
248  break;
249  default:
250  ;
251  }
252  }
253  const auto tags = m_fileInfo.tags();
254  switch(m_tagStatus) {
255  case TagStatus::Original:
256  CPPUNIT_ASSERT(tags.size() == 1);
257  CPPUNIT_ASSERT(tags.front()->value(KnownField::Title).toString() == "Big Buck Bunny - test 8");
258  CPPUNIT_ASSERT(tags.front()->value(KnownField::Artist).isEmpty());
259  CPPUNIT_ASSERT(tags.front()->value(KnownField::Comment).toString() == "Matroska Validation File 8, secondary audio commentary track, misc subtitle tracks");
260  break;
262  checkMkvTestMetaData();
263  break;
264  case TagStatus::Removed:
265  CPPUNIT_ASSERT_EQUAL(0_st, tags.size());
266  }
267  CPPUNIT_ASSERT(m_fileInfo.worstNotificationTypeIncludingRelatedObjects() <= NotificationType::Information);
268 }
269 
273 void OverallTests::checkMkvTestfile6()
274 {
275  CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
276  CPPUNIT_ASSERT_EQUAL(TimeSpan::fromMinutes(1) + TimeSpan::fromSeconds(27) + TimeSpan::fromMilliseconds(336), m_fileInfo.duration());
277  const auto tracks = m_fileInfo.tracks();
278  CPPUNIT_ASSERT_EQUAL(2_st, tracks.size());
279  for(const auto &track : tracks) {
280  switch(track->id()) {
281  case 2422994868:
282  CPPUNIT_ASSERT(track->mediaType() == MediaType::Video);
283  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::MicrosoftMpeg4);
284  CPPUNIT_ASSERT(track->pixelSize() == Size(854, 480));
285  break;
286  case 3653291187:
287  CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
288  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Mpeg1Audio);
289  CPPUNIT_ASSERT(track->samplingFrequency() == 48000);
290  CPPUNIT_ASSERT(track->channelConfig() == static_cast<byte>(MpegChannelMode::Stereo));
291  break;
292  default:
293  CPPUNIT_FAIL("unknown track ID");
294  }
295  }
296  const auto tags = m_fileInfo.tags();
297  switch(m_tagStatus) {
298  case TagStatus::Original:
299  CPPUNIT_ASSERT(tags.size() == 1);
300  CPPUNIT_ASSERT(tags.front()->value(KnownField::Title).toString() == "Big Buck Bunny - test 6");
301  CPPUNIT_ASSERT(tags.front()->value(KnownField::Artist).isEmpty());
302  CPPUNIT_ASSERT(tags.front()->value(KnownField::Comment).toString() == "Matroska Validation File 6, random length to code the size of Clusters and Blocks, no Cues for seeking");
303  break;
305  checkMkvTestMetaData();
306  break;
307  case TagStatus::Removed:
308  CPPUNIT_ASSERT_EQUAL(0_st, tags.size());
309  }
310  CPPUNIT_ASSERT(m_fileInfo.worstNotificationTypeIncludingRelatedObjects() <= NotificationType::Information);
311 }
312 
316 void OverallTests::checkMkvTestfile7()
317 {
318  CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
319  CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(37) + TimeSpan::fromMilliseconds(43), m_fileInfo.duration());
320  const auto tracks = m_fileInfo.tracks();
321  CPPUNIT_ASSERT_EQUAL(2_st, tracks.size());
322  for(const auto &track : tracks) {
323  switch(track->id()) {
324  case 568001708:
325  CPPUNIT_ASSERT(track->mediaType() == MediaType::Video);
326  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Avc);
327  CPPUNIT_ASSERT(track->pixelSize() == Size(1024, 576));
328  CPPUNIT_ASSERT(!strcmp(track->chromaFormat(), "YUV 4:2:0"));
329  break;
330  case 2088735154:
331  CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
332  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Aac);
333  CPPUNIT_ASSERT(track->samplingFrequency() == 48000);
334  CPPUNIT_ASSERT(track->channelConfig() == Mpeg4ChannelConfigs::FrontLeftFrontRight);
335  break;
336  default:
337  CPPUNIT_FAIL("unknown track ID");
338  }
339  }
340  const auto tags = m_fileInfo.tags();
341  switch(m_tagStatus) {
342  case TagStatus::Original:
343  CPPUNIT_ASSERT_EQUAL(1_st, tags.size());
344  CPPUNIT_ASSERT_EQUAL("Big Buck Bunny - test 7"s, tags.front()->value(KnownField::Title).toString());
345  CPPUNIT_ASSERT(tags.front()->value(KnownField::Artist).isEmpty());
346  CPPUNIT_ASSERT_EQUAL("Matroska Validation File 7, junk elements are present at the beggining or end of clusters, the parser should skip it. There is also a damaged element at 451418"s, tags.front()->value(KnownField::Comment).toString());
347  break;
349  checkMkvTestMetaData();
350  break;
351  case TagStatus::Removed:
352  CPPUNIT_ASSERT_EQUAL(0_st, tags.size());
353  }
354 
355  for(const Notification &notification : m_fileInfo.gatherRelatedNotifications()) {
356  if(notification.type() != NotificationType::Warning) {
357  continue;
358  }
359  CPPUNIT_ASSERT(startsWith(notification.context(), "parsing header of EBML element 0xEA \"cue codec state\" at"));
360  CPPUNIT_ASSERT_EQUAL("Data of EBML element seems to be truncated; unable to parse siblings of that element."s, notification.message());
361  }
362  CPPUNIT_ASSERT(m_fileInfo.worstNotificationTypeIncludingRelatedObjects() <= NotificationType::Warning);
363 }
364 
368 void OverallTests::checkMkvTestfile8()
369 {
370  CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
371  CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(47) + TimeSpan::fromMilliseconds(341), m_fileInfo.duration());
372  const auto tracks = m_fileInfo.tracks();
373  CPPUNIT_ASSERT_EQUAL(2_st, tracks.size());
374  for(const auto &track : tracks) {
375  switch(track->id()) {
376  case 568001708:
377  CPPUNIT_ASSERT(track->mediaType() == MediaType::Video);
378  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Avc);
379  CPPUNIT_ASSERT(track->pixelSize() == Size(1024, 576));
380  CPPUNIT_ASSERT(!strcmp(track->chromaFormat(), "YUV 4:2:0"));
381  break;
382  case 2088735154:
383  CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
384  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Aac);
385  CPPUNIT_ASSERT(track->samplingFrequency() == 48000);
386  CPPUNIT_ASSERT(track->channelConfig() == Mpeg4ChannelConfigs::FrontLeftFrontRight);
387  break;
388  default:
389  CPPUNIT_FAIL("unknown track ID");
390  }
391  }
392  const auto tags = m_fileInfo.tags();
393  switch(m_tagStatus) {
394  case TagStatus::Original:
395  CPPUNIT_ASSERT_EQUAL(1_st, tags.size());
396  CPPUNIT_ASSERT_EQUAL("Big Buck Bunny - test 8"s, tags.front()->value(KnownField::Title).toString());
397  CPPUNIT_ASSERT(tags.front()->value(KnownField::Artist).isEmpty());
398  CPPUNIT_ASSERT_EQUAL("Matroska Validation File 8, audio missing between timecodes 6.019s and 6.360s"s, tags.front()->value(KnownField::Comment).toString());
399  break;
401  checkMkvTestMetaData();
402  break;
403  case TagStatus::Removed:
404  CPPUNIT_ASSERT_EQUAL(0_st, tags.size());
405  }
406  CPPUNIT_ASSERT(m_fileInfo.worstNotificationTypeIncludingRelatedObjects() <= NotificationType::Information);
407 }
408 
412 void OverallTests::checkMkvTestfileHandbrakeChapters()
413 {
414  CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
415  CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(27) + TimeSpan::fromMilliseconds(569), m_fileInfo.duration());
416  const auto tracks = m_fileInfo.tracks();
417  CPPUNIT_ASSERT_EQUAL(2_st, tracks.size());
418  for(const auto &track : tracks) {
419  switch(track->id()) {
420  case 1:
421  CPPUNIT_ASSERT(track->mediaType() == MediaType::Video);
422  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Avc);
423  CPPUNIT_ASSERT_EQUAL(4.0, track->version());
424  CPPUNIT_ASSERT(track->pixelSize() == Size(1280, 544));
425  CPPUNIT_ASSERT(track->displaySize() == Size(1280, 544));
426  CPPUNIT_ASSERT(track->fps() == 23);
427  break;
428  case 2:
429  CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
430  CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Aac);
431  CPPUNIT_ASSERT(track->samplingFrequency() == 44100);
432  CPPUNIT_ASSERT(track->channelConfig() == Mpeg4ChannelConfigs::FrontLeftFrontRight);
433  break;
434  default:
435  CPPUNIT_FAIL(argsToString("unknown track ID ", track->id()));
436  }
437  }
438  const auto chapters = m_fileInfo.chapters();
439  CPPUNIT_ASSERT_EQUAL(2_st, chapters.size());
440  for(const auto &chapter : chapters) {
441  switch(chapter->id()) {
442  case 1:
443  CPPUNIT_ASSERT(!strcmp(chapter->names().at(0).data(), "Kapitel 01"));
444  CPPUNIT_ASSERT_EQUAL(0l, chapter->startTime().totalTicks());
445  CPPUNIT_ASSERT_EQUAL(15, chapter->endTime().seconds());
446  break;
447  case 2:
448  CPPUNIT_ASSERT(!strcmp(chapter->names().at(0).data(), "Kapitel 02"));
449  CPPUNIT_ASSERT_EQUAL(15, chapter->startTime().seconds());
450  CPPUNIT_ASSERT_EQUAL(27, chapter->endTime().seconds());
451  break;
452  default:
453  CPPUNIT_FAIL(argsToString("unknown chapter ID ", chapter->id()));
454  }
455  }
456  const auto tags = m_fileInfo.tags();
457  switch(m_tagStatus) {
458  case TagStatus::Original:
459  CPPUNIT_ASSERT_EQUAL(2_st, tags.size());
460  CPPUNIT_ASSERT(tags[0]->target().isEmpty());
461  CPPUNIT_ASSERT_EQUAL(""s, static_cast<MatroskaTag *>(tags[0])->value("CREATION_TIME").toString());
462  CPPUNIT_ASSERT_EQUAL("Lavf55.12.0"s, tags[0]->value(KnownField::Encoder).toString());
463  CPPUNIT_ASSERT_EQUAL(2_st, tags[1]->target().tracks().at(0));
464  CPPUNIT_ASSERT_EQUAL("eng"s, tags[1]->value(KnownField::Language).toString());
465  break;
467  checkMkvTestMetaData();
468  break;
469  case TagStatus::Removed:
470  CPPUNIT_ASSERT_EQUAL(0_st, tags.size());
471  }
472  CPPUNIT_ASSERT(m_fileInfo.worstNotificationTypeIncludingRelatedObjects() <= NotificationType::Information);
473 }
474 
478 void OverallTests::checkMkvTestfileNestedTags()
479 {
480  CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
481  const auto tags = m_fileInfo.tags();
482  bool generalTagFound = false;
483  switch(m_tagStatus) {
484  case TagStatus::Original:
486  CPPUNIT_ASSERT_EQUAL(5_st, tags.size());
487  for(const Tag *tag : tags) {
488  CPPUNIT_ASSERT(tag->type() == TagType::MatroskaTag);
489  const auto *mkvTag = static_cast<const MatroskaTag *>(tag);
490  const auto &target = mkvTag->target();
491  if(target.level() == 50 && target.tracks().empty()) {
492  generalTagFound = true;
493  CPPUNIT_ASSERT_EQUAL("Vanilla Sky"s, tag->value(KnownField::Title).toString());
494  const auto &fields = mkvTag->fields();
495  const auto &artistField = fields.find(mkvTag->fieldId(KnownField::Artist));
496  CPPUNIT_ASSERT(artistField != fields.end());
497  CPPUNIT_ASSERT_EQUAL("Test artist"s, artistField->second.value().toString());
498  const auto &nestedFields = artistField->second.nestedFields();
499  CPPUNIT_ASSERT_EQUAL(1_st, nestedFields.size());
500  CPPUNIT_ASSERT_EQUAL("ADDRESS"s, nestedFields[0].idToString());
501  CPPUNIT_ASSERT_EQUAL("Test address"s, nestedFields[0].value().toString());
502  }
503  }
504  CPPUNIT_ASSERT(generalTagFound);
505  break;
506  case TagStatus::Removed:
507  CPPUNIT_ASSERT_EQUAL(0_st, tags.size());
508  }
509 
510  // the file contains in fact the unknown element [44][B4]
511  // TODO: find out what this element is about (its data is only the single byte 0x01)
512  for(const Notification &notification : m_fileInfo.gatherRelatedNotifications()) {
513  if(notification.type() != NotificationType::Warning) {
514  continue;
515  }
516  CPPUNIT_ASSERT(startsWith(notification.message(), "\"SimpleTag\"-element contains unknown element 0x44B4 at"));
517  }
518  CPPUNIT_ASSERT(m_fileInfo.worstNotificationTypeIncludingRelatedObjects() <= NotificationType::Warning);
519 }
520 
524 void OverallTests::checkMkvTestMetaData()
525 {
526  // check tags
527  const auto tags = m_fileInfo.tags();
528  const auto tracks = m_fileInfo.tracks();
529  CPPUNIT_ASSERT_EQUAL(2_st, tags.size());
530  CPPUNIT_ASSERT_EQUAL(m_testTitle.toString(), tags.front()->value(KnownField::Title).toString());
531  CPPUNIT_ASSERT(tags.front()->value(KnownField::Artist).isEmpty());
532  CPPUNIT_ASSERT_EQUAL(m_testComment.toString(), tags.front()->value(KnownField::Comment).toString());
533  CPPUNIT_ASSERT_EQUAL(30_st, tags[1]->target().level());
534  CPPUNIT_ASSERT_EQUAL(tracks.at(0)->id(), tags[1]->target().tracks().at(0));
535  CPPUNIT_ASSERT_EQUAL(m_testAlbum.toString(), tags[1]->value(KnownField::Album).toString());
536  CPPUNIT_ASSERT_EQUAL(m_testPartNumber.toInteger(), tags[1]->value(KnownField::PartNumber).toInteger());
537  CPPUNIT_ASSERT_EQUAL(m_testTotalParts.toInteger(), tags[1]->value(KnownField::TotalParts).toInteger());
538 
539  // check attachments
540  const auto attachments = m_fileInfo.attachments();
541  CPPUNIT_ASSERT_EQUAL(1_st, attachments.size());
542  CPPUNIT_ASSERT(attachments[0]->mimeType() == "image/png");
543  CPPUNIT_ASSERT(attachments[0]->name() == "cover.jpg");
544  const StreamDataBlock *attachmentData = attachments[0]->data();
545  CPPUNIT_ASSERT(attachmentData != nullptr);
546  if (m_testCover.empty()) {
547  m_testCover = readFile(testFilePath("matroska_wave1/logo3_256x256.png"), 20000);
548  }
549  CPPUNIT_ASSERT_EQUAL(m_testCover.size(), static_cast<size_t>(attachmentData->size()));
550  istream &attachmentSteam = attachmentData->stream();
551  attachmentSteam.seekg(attachmentData->startOffset());
552  for (char expectedChar : m_testCover) {
553  CPPUNIT_ASSERT_EQUAL(expectedChar, static_cast<char>(attachmentSteam.get()));
554  }
555 }
556 
560 void OverallTests::checkMkvConstraints()
561 {
562  using namespace MkvTestFlags;
563 
564  CPPUNIT_ASSERT(m_fileInfo.container());
565  if(m_mode & PaddingConstraints) {
566  if(m_mode & ForceRewring) {
567  CPPUNIT_ASSERT_EQUAL(4096_st, m_fileInfo.paddingSize());
568  } else {
569  CPPUNIT_ASSERT(m_fileInfo.paddingSize() >= 1024);
570  CPPUNIT_ASSERT(m_fileInfo.paddingSize() <= (4096 + 1024));
571  }
572  if(!(m_mode & RemoveTag) && (m_expectedTagPos != ElementPosition::Keep) && ((m_mode & ForceRewring) || (m_mode & ForceTagPos))) {
573  CPPUNIT_ASSERT_EQUAL(m_expectedTagPos, m_fileInfo.container()->determineTagPosition());
574  }
575  if((m_expectedIndexPos != ElementPosition::Keep) && ((m_mode & ForceRewring) || (m_mode & ForceIndexPos))) {
576  CPPUNIT_ASSERT_EQUAL(m_expectedIndexPos, m_fileInfo.container()->determineIndexPosition());
577  }
578  }
579 }
580 
584 void OverallTests::setMkvTestMetaData()
585 {
586  CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
587  auto *container = static_cast<MatroskaContainer *>(m_fileInfo.container());
588 
589  // change the present tag
590  const string fileName(m_fileInfo.fileName());
591  if(fileName == "test4.mkv") {
592  // test4.mkv has no tag, so one must be created first
593  container->createTag(TagTarget(50));
594  // also change language, name, forced and default of track "3171450505" to German
595  MatroskaTrack *track = container->trackById(3171450505);
596  CPPUNIT_ASSERT(track);
597  track->setLanguage("ger");
598  track->setName("the name");
599  track->setDefault(true);
600  track->setEnabled(true);
601  track->setForced(true);
602  } else if(fileName == "handbrake-chapters-2.mkv") {
603  // remove 2nd tag
604  m_fileInfo.removeTag(m_fileInfo.tags().at(1));
605  }
606  Tag *firstTag = m_fileInfo.tags().at(0);
607  firstTag->setValue(KnownField::Title, m_testTitle);
608  firstTag->setValue(KnownField::Comment, m_testComment);
609  // add an additional tag targeting the first track
611  trackIds.emplace_back(m_fileInfo.tracks().at(0)->id());
612  Tag *newTag = container->createTag(TagTarget(30, trackIds));
613  CPPUNIT_ASSERT_MESSAGE("create tag", newTag);
614  newTag->setValue(KnownField::Album, m_testAlbum);
615  newTag->setValue(KnownField::PartNumber, m_testPartNumber);
616  newTag->setValue(KnownField::TotalParts, m_testTotalParts);
617  // assign an attachment
618  AbstractAttachment *attachment = container->createAttachment();
619  CPPUNIT_ASSERT_MESSAGE("create attachment", attachment);
620  attachment->setFile(TestUtilities::testFilePath("matroska_wave1/logo3_256x256.png"));
621  attachment->setMimeType("image/png");
622  attachment->setName("cover.jpg");
623 }
624 
630 void OverallTests::createMkvWithNestedTags()
631 {
632 #ifdef PLATFORM_UNIX
633  m_nestedTagsMkvPath = workingCopyPathMode("mtx-test-data/mkv/nested-tags.mkv", WorkingCopyMode::NoCopy);
634  remove(m_nestedTagsMkvPath.data());
635 
636  cerr << "\n\n- Create testfile \"" << m_nestedTagsMkvPath << "\" with mkvmerge" << endl;
637  const string tagsMkvPath(testFilePath("mtx-test-data/mkv/tags.mkv"));
638  const string tagsXmlPath(testFilePath("mkv/nested-tags.xml"));
639  const char *const mkvmergeArgs[] = {
640  "--ui-language en_US",
641  "--output", m_nestedTagsMkvPath.data(),
642  "--no-global-tags", "--language", "0:und", "--default-track", "0:yes", "--language", "1:und", "--default-track", "1:yes",
643  "(", tagsMkvPath.data(), ")",
644  "--global-tags", tagsXmlPath.data(), "--track-order", "0:0,0:1", nullptr
645  };
646  string mkvmergeOutput, mkvmergeErrors;
647  int res = execHelperApp("/bin/mkvmerge", mkvmergeArgs, mkvmergeOutput, mkvmergeErrors);
648  cout << mkvmergeOutput << endl;
649  cerr << mkvmergeErrors << endl;
650  if(res) {
651  cerr << "- failure (exit code " << res << "); unable to test nested tags" << endl;
652  remove(m_nestedTagsMkvPath.data());
653  m_nestedTagsMkvPath.clear();
654  }
655 #endif
656 }
657 
662 {
663  cerr << endl << "Matroska parser" << endl;
664  m_fileInfo.setForceFullParse(false);
665  m_tagStatus = TagStatus::Original;
666  parseFile(TestUtilities::testFilePath("matroska_wave1/test1.mkv"), &OverallTests::checkMkvTestfile1);
667  parseFile(TestUtilities::testFilePath("matroska_wave1/test2.mkv"), &OverallTests::checkMkvTestfile2);
668  parseFile(TestUtilities::testFilePath("matroska_wave1/test3.mkv"), &OverallTests::checkMkvTestfile3);
669  parseFile(TestUtilities::testFilePath("matroska_wave1/test4.mkv"), &OverallTests::checkMkvTestfile4);
670  parseFile(TestUtilities::testFilePath("matroska_wave1/test5.mkv"), &OverallTests::checkMkvTestfile5);
671  parseFile(TestUtilities::testFilePath("matroska_wave1/test6.mkv"), &OverallTests::checkMkvTestfile6);
672  parseFile(TestUtilities::testFilePath("matroska_wave1/test7.mkv"), &OverallTests::checkMkvTestfile7);
673  parseFile(TestUtilities::testFilePath("matroska_wave1/test8.mkv"), &OverallTests::checkMkvTestfile8);
674  parseFile(TestUtilities::testFilePath("mtx-test-data/mkv/handbrake-chapters-2.mkv"), &OverallTests::checkMkvTestfileHandbrakeChapters);
675  createMkvWithNestedTags();
676  if(!m_nestedTagsMkvPath.empty()) {
677  parseFile(m_nestedTagsMkvPath, &OverallTests::checkMkvTestfileNestedTags);
678  }
679 }
680 
681 #ifdef PLATFORM_UNIX
682 
689 void OverallTests::testMkvMakingWithDifferentSettings()
690 {
691  // full parse is required to determine padding
692  m_fileInfo.setForceFullParse(true);
693 
694  // do the test under different conditions
695  for(m_mode = 0; m_mode != 0x100; ++m_mode) {
696  using namespace MkvTestFlags;
697 
698  // setup test conditions
699  m_fileInfo.setForceRewrite(m_mode & ForceRewring);
700  if(m_mode & KeepTagPos) {
701  m_fileInfo.setTagPosition(ElementPosition::Keep);
702  } else {
703  m_fileInfo.setTagPosition(m_mode & TagsBeforeData ? ElementPosition::BeforeData : ElementPosition::AfterData);
704  }
705  if(m_mode & KeepIndexPos) {
706  if(m_mode & IndexBeforeData) {
707  continue;
708  }
709  m_fileInfo.setIndexPosition(ElementPosition::Keep);
710  } else {
711  m_fileInfo.setIndexPosition(m_mode & IndexBeforeData ? ElementPosition::BeforeData : ElementPosition::AfterData);
712  }
713  m_fileInfo.setPreferredPadding(m_mode & PaddingConstraints ? 4096 : 0);
714  m_fileInfo.setMinPadding(m_mode & PaddingConstraints ? 1024 : 0);
715  m_fileInfo.setMaxPadding(m_mode & PaddingConstraints ? (4096 + 1024) : static_cast<size_t>(-1));
716  m_fileInfo.setForceTagPosition(m_mode & ForceTagPos);
717  m_fileInfo.setForceIndexPosition(m_mode & ForceIndexPos);
718 
719  // print test conditions
720  list<string> testConditions;
721  if(m_mode & ForceRewring) {
722  testConditions.emplace_back("forcing rewrite");
723  }
724  if(m_mode & KeepTagPos) {
725  if(m_mode & RemoveTag) {
726  testConditions.emplace_back("removing tag");
727  } else {
728  testConditions.emplace_back("keeping tag position");
729  }
730  } else if(m_mode & TagsBeforeData) {
731  testConditions.emplace_back("tags before data");
732  } else {
733  testConditions.emplace_back("tags after data");
734  }
735  if(m_mode & KeepIndexPos) {
736  testConditions.emplace_back("keeping index position");
737  } else if(m_mode & IndexBeforeData) {
738  testConditions.emplace_back("index before data");
739  } else {
740  testConditions.emplace_back("index after data");
741  }
742  if(m_mode & PaddingConstraints) {
743  testConditions.emplace_back("padding constraints");
744  }
745  if(m_mode & ForceTagPos) {
746  testConditions.emplace_back("forcing tag position");
747  }
748  if(m_mode & ForceIndexPos) {
749  testConditions.emplace_back("forcing index position");
750  }
751  cerr << endl << "Matroska maker - testmode " << m_mode << ": " << joinStrings(testConditions, ", ") << endl;
752 
753  // do actual tests
754  m_tagStatus = (m_mode & RemoveTag) ? TagStatus::Removed : TagStatus::TestMetaDataPresent;
755  void (OverallTests::*modifyRoutine)(void) = (m_mode & RemoveTag) ? &OverallTests::removeAllTags : &OverallTests::setMkvTestMetaData;
756  makeFile(TestUtilities::workingCopyPath("matroska_wave1/test1.mkv"), modifyRoutine, &OverallTests::checkMkvTestfile1);
757  makeFile(TestUtilities::workingCopyPath("matroska_wave1/test2.mkv"), modifyRoutine, &OverallTests::checkMkvTestfile2);
758  makeFile(TestUtilities::workingCopyPath("matroska_wave1/test3.mkv"), modifyRoutine, &OverallTests::checkMkvTestfile3);
759  makeFile(TestUtilities::workingCopyPath("matroska_wave1/test4.mkv"), modifyRoutine, &OverallTests::checkMkvTestfile4);
760  makeFile(TestUtilities::workingCopyPath("matroska_wave1/test5.mkv"), modifyRoutine, &OverallTests::checkMkvTestfile5);
761  makeFile(TestUtilities::workingCopyPath("matroska_wave1/test6.mkv"), modifyRoutine, &OverallTests::checkMkvTestfile6);
762  makeFile(TestUtilities::workingCopyPath("matroska_wave1/test7.mkv"), modifyRoutine, &OverallTests::checkMkvTestfile7);
763  makeFile(TestUtilities::workingCopyPath("matroska_wave1/test8.mkv"), modifyRoutine, &OverallTests::checkMkvTestfile8);
764  makeFile(TestUtilities::workingCopyPath("mtx-test-data/mkv/handbrake-chapters-2.mkv"), modifyRoutine, &OverallTests::checkMkvTestfileHandbrakeChapters);
765  }
766 }
767 
772 void OverallTests::testMkvMakingNestedTags()
773 {
774  createMkvWithNestedTags();
775  if(!m_nestedTagsMkvPath.empty()) {
776  cerr << endl << "Matroska maker - rewrite file with nested tags" << endl;
777  m_fileInfo.setMinPadding(0);
778  m_fileInfo.setMaxPadding(0);
779  m_fileInfo.setTagPosition(ElementPosition::BeforeData);
780  m_fileInfo.setIndexPosition(ElementPosition::BeforeData);
781  makeFile(m_nestedTagsMkvPath, &OverallTests::noop, &OverallTests::checkMkvTestfileNestedTags);
782  }
783 }
784 #endif
std::vector< IdType > IdContainerType
Definition: tagtarget.h:35
const TagTarget & target() const
Returns the target of tag.
Definition: tag.h:245
void setFile(const std::string &path)
Sets the data, name and MIME-type for the specified path.
void setDefault(bool isDefault)
Sets whether the track is a default track.
Implementation of GenericContainer<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>.
The Size class defines the size of a two-dimensional object using integer point precision.
Definition: size.h:16
void setForced(bool forced)
Sets whether the track is forced.
The StreamDataBlock class is a reference to a certain data block of a stream.
void setEnabled(bool enabled)
Sets whether the track is enabled.
std::istream::pos_type startOffset() const
Returns the absolute start offset of the data block in the stream.
The AbstractAttachment class parses and stores attachment information.
The OverallTests class tests reading and writing tags and parsing technical information for all suppo...
Definition: overall.h:44
Implementation of Media::AbstractTrack for the Matroska container.
Definition: matroskatrack.h:47
The Tag class is used to store, read and write tag information.
Definition: tag.h:98
void setName(const std::string &name)
Sets the name.
The Notification class holds a notification message of a certain notification type.
Definition: notification.h:43
void testMkvParsing()
Tests the Matroska parser via MediaFileInfo.
Definition: overallmkv.cpp:661
std::istream & stream() const
Returns the associated stream.
Implementation of Media::Tag for the Matroska container.
Definition: matroskatag.h:50
void setName(const std::string &name)
Sets the (file) name of the attachment.
void setMimeType(const std::string &mimeType)
Sets the MIME-type of the attachment.
virtual bool setValue(KnownField field, const TagValue &value)=0
Assigns the given value to the specified field.
The TagTarget class specifies the target of a tag.
Definition: tagtarget.h:31
std::istream::pos_type size() const
Returns the size of the data block.
TagType * createTag(const TagTarget &target=TagTarget())
Creates and returns a tag for the specified target.
void setLanguage(const std::string &language)
Sets the language of the track.