Tag Parser  6.2.2
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
matroskacontainer.cpp
Go to the documentation of this file.
1 #include "./matroskacontainer.h"
2 #include "./ebmlid.h"
3 #include "./matroskaid.h"
4 #include "./matroskacues.h"
6 #include "./matroskaseekinfo.h"
7 
8 #include "../mediafileinfo.h"
9 #include "../exceptions.h"
10 #include "../backuphelper.h"
11 
12 #include "resources/config.h"
13 
14 #include <c++utilities/conversion/stringconversion.h>
15 #include <c++utilities/conversion/stringbuilder.h>
16 #include <c++utilities/io/catchiofailure.h>
17 
18 #include <unistd.h>
19 
20 #include <functional>
21 #include <initializer_list>
22 #include <unordered_set>
23 #include <memory>
24 
25 using namespace std;
26 using namespace std::placeholders;
27 using namespace IoUtilities;
28 using namespace ConversionUtilities;
29 using namespace ChronoUtilities;
30 
31 namespace Media {
32 
33 constexpr const char appInfo[] = APP_NAME " v" APP_VERSION;
34 constexpr uint64 appInfoElementDataSize = sizeof(appInfo) - 1;
36 
42 uint64 MatroskaContainer::m_maxFullParseSize = 0x3200000;
43 
47 MatroskaContainer::MatroskaContainer(MediaFileInfo &fileInfo, uint64 startOffset) :
49  m_maxIdLength(4),
50  m_maxSizeLength(8),
51  m_segmentCount(0)
52 {
53  m_version = 1;
54  m_readVersion = 1;
55  m_doctype = "matroska";
56  m_doctypeVersion = 1;
58 }
59 
61 {}
62 
64 {
66  m_maxIdLength = 4;
67  m_maxSizeLength = 8;
68  m_version = 1;
69  m_readVersion = 1;
70  m_doctype = "matroska";
71  m_doctypeVersion = 1;
73  m_tracksElements.clear();
74  m_segmentInfoElements.clear();
75  m_tagsElements.clear();
76  m_chaptersElements.clear();
77  m_attachmentsElements.clear();
78  m_seekInfos.clear();
79  m_editionEntries.clear();
80  m_attachments.clear();
81  m_segmentCount = 0;
82 }
83 
89 {
90  static const string context("validating Matroska file index (cues)");
91  bool cuesElementsFound = false;
92  if(m_firstElement) {
93  unordered_set<int> ids;
94  bool cueTimeFound = false, cueTrackPositionsFound = false;
95  unique_ptr<EbmlElement> clusterElement;
96  uint64 pos, prevClusterSize = 0, currentOffset = 0;
97  // iterate throught all segments
98  for(EbmlElement *segmentElement = m_firstElement->siblingById(MatroskaIds::Segment); segmentElement; segmentElement = segmentElement->siblingById(MatroskaIds::Segment)) {
99  segmentElement->parse();
100  // iterate throught all child elements of the segment (only "Cues"- and "Cluster"-elements are relevant for this method)
101  for(EbmlElement *segmentChildElement = segmentElement->firstChild(); segmentChildElement; segmentChildElement = segmentChildElement->nextSibling()) {
102  segmentChildElement->parse();
103  switch(segmentChildElement->id()) {
104  case EbmlIds::Void:
105  case EbmlIds::Crc32:
106  break;
107  case MatroskaIds::Cues:
108  cuesElementsFound = true;
109  // parse childs of "Cues"-element ("CuePoint"-elements)
110  for(EbmlElement *cuePointElement = segmentChildElement->firstChild(); cuePointElement; cuePointElement = cuePointElement->nextSibling()) {
111  cuePointElement->parse();
112  cueTimeFound = cueTrackPositionsFound = false; // to validate quantity of these elements
113  switch(cuePointElement->id()) {
114  case EbmlIds::Void:
115  case EbmlIds::Crc32:
116  break;
118  // parse childs of "CuePoint"-element
119  for(EbmlElement *cuePointChildElement = cuePointElement->firstChild(); cuePointChildElement; cuePointChildElement = cuePointChildElement->nextSibling()) {
120  cuePointChildElement->parse();
121  switch(cuePointChildElement->id()) {
123  // validate uniqueness
124  if(cueTimeFound) {
125  addNotification(NotificationType::Warning, "\"CuePoint\"-element contains multiple \"CueTime\" elements.", context);
126  } else {
127  cueTimeFound = true;
128  }
129  break;
131  cueTrackPositionsFound = true;
132  ids.clear();
133  clusterElement.reset();
134  for(EbmlElement *subElement = cuePointChildElement->firstChild(); subElement; subElement = subElement->nextSibling()) {
135  subElement->parse();
136  switch(subElement->id()) {
143  // validate uniqueness
144  if(ids.count(subElement->id())) {
145  addNotification(NotificationType::Warning, "\"CueTrackPositions\"-element contains multiple \"" % subElement->idToString() + "\" elements.", context);
146  } else {
147  ids.insert(subElement->id());
148  }
149  break;
150  case EbmlIds::Crc32:
151  case EbmlIds::Void:
153  break;
154  default:
155  addNotification(NotificationType::Warning, "\"CueTrackPositions\"-element contains unknown element \"" % subElement->idToString() + "\".", context);
156  }
157  switch(subElement->id()) {
158  case EbmlIds::Void:
159  case EbmlIds::Crc32:
161  break;
163  // validate "Cluster" position denoted by "CueClusterPosition"-element
164  clusterElement = make_unique<EbmlElement>(*this, segmentElement->dataOffset() + subElement->readUInteger() - currentOffset);
165  try {
166  clusterElement->parse();
167  if(clusterElement->id() != MatroskaIds::Cluster) {
168  addNotification(NotificationType::Critical, "\"CueClusterPosition\" element at " % numberToString(subElement->startOffset()) + " does not point to \"Cluster\"-element (points to " + numberToString(clusterElement->startOffset()) + ").", context);
169  }
170  } catch(const Failure &) {
171  addNotifications(context, *clusterElement);
172  }
173  break;
175  // read "Block" position denoted by "CueRelativePosition"-element (validate later since the "Cluster"-element is needed to validate)
176  pos = subElement->readUInteger();
177  break;
179  break;
181  break;
183  break;
185  break;
186  default:
187  ;
188  }
189  }
190  // validate existence of mandatory elements
191  if(!ids.count(MatroskaIds::CueTrack)) {
192  addNotification(NotificationType::Warning, "\"CueTrackPositions\"-element does not contain mandatory element \"CueTrack\".", context);
193  }
194  if(!clusterElement) {
195  addNotification(NotificationType::Warning, "\"CueTrackPositions\"-element does not contain mandatory element \"CueClusterPosition\".", context);
196  } else {
197  if(ids.count(MatroskaIds::CueRelativePosition)) {
198  // validate "Block" position denoted by "CueRelativePosition"-element
199  EbmlElement referenceElement(*this, clusterElement->dataOffset() + pos);
200  try {
201  referenceElement.parse();
202  switch(referenceElement.id()) {
204  case MatroskaIds::Block:
206  break;
207  default:
208  addNotification(NotificationType::Critical, "\"CueRelativePosition\" element does not point to \"Block\"-, \"BlockGroup\", or \"SimpleBlock\"-element (points to " % numberToString(referenceElement.startOffset()) + ").", context);
209  }
210  } catch(const Failure &) {
211  addNotifications(context, referenceElement);
212  }
213  }
214  }
215  break;
216  case EbmlIds::Crc32:
217  case EbmlIds::Void:
218  break;
219  default:
220  addNotification(NotificationType::Warning, "\"CuePoint\"-element contains unknown element \"" % cuePointElement->idToString() + "\".", context);
221  }
222  }
223  // validate existence of mandatory elements
224  if(!cueTimeFound) {
225  addNotification(NotificationType::Warning, "\"CuePoint\"-element does not contain mandatory element \"CueTime\".", context);
226  }
227  if(!cueTrackPositionsFound) {
228  addNotification(NotificationType::Warning, "\"CuePoint\"-element does not contain mandatory element \"CueClusterPosition\".", context);
229  }
230  break;
231  default:
232  ;
233  }
234  }
235  break;
237  // parse childs of "Cluster"-element
238  for(EbmlElement *clusterElementChild = segmentChildElement->firstChild(); clusterElementChild; clusterElementChild = clusterElementChild->nextSibling()) {
239  clusterElementChild->parse();
240  switch(clusterElementChild->id()) {
241  case EbmlIds::Void:
242  case EbmlIds::Crc32:
243  break;
245  // validate position
246  if((pos = clusterElementChild->readUInteger()) > 0 && (segmentChildElement->startOffset() - segmentElement->dataOffset() + currentOffset) != pos) {
247  addNotification(NotificationType::Critical, "\"Position\"-element at " % numberToString(clusterElementChild->startOffset()) % " points to " % numberToString(pos) + " which is not the offset of the containing \"Cluster\"-element.", context);
248  }
249  break;
251  // validate prev size
252  if(clusterElementChild->readUInteger() != prevClusterSize) {
253  addNotification(NotificationType::Critical, "\"PrevSize\"-element at " % numberToString(clusterElementChild->startOffset()) + " has invalid value.", context);
254  }
255  break;
256  default:
257  ;
258  }
259  }
260  prevClusterSize = segmentChildElement->totalSize();
261  break;
262  default:
263  ;
264  }
265  }
266  currentOffset += segmentElement->totalSize();
267  }
268  }
269  // add a warning when no index could be found
270  if(!cuesElementsFound) {
271  addNotification(NotificationType::Warning, "No \"Cues\"-elements (index) found.", context);
272  }
273 }
274 
278 bool sameOffset(uint64 offset, const EbmlElement *element) {
279  return element->startOffset() == offset;
280 }
281 
286 inline bool excludesOffset(const vector<EbmlElement *> &elements, uint64 offset)
287 {
288  return find_if(elements.cbegin(), elements.cend(), std::bind(sameOffset, offset, _1)) == elements.cend();
289 }
290 
292 {
293  for(const auto &entry : m_editionEntries) {
294  const auto &chapters = entry->chapters();
295  if(index < chapters.size()) {
296  return chapters[index].get();
297  } else {
298  index -= chapters.size();
299  }
300  }
301  return nullptr;
302 }
303 
305 {
306  size_t count = 0;
307  for(const auto &entry : m_editionEntries) {
308  count += entry->chapters().size();
309  }
310  return count;
311 }
312 
314 {
315  // generate unique ID
316  srand(time(nullptr));
317  byte tries = 0;
318  uint64 attachmentId;
319 generateRandomId:
320  attachmentId = rand();
321  if(tries < 0xFF) {
322  for(const auto &attachment : m_attachments) {
323  if(attachmentId == attachment->id()) {
324  ++tries;
325  goto generateRandomId;
326  }
327  }
328  }
329  // create new attachment, set ID
330  m_attachments.emplace_back(make_unique<MatroskaAttachment>());
331  auto &attachment = m_attachments.back();
332  attachment->setId(attachmentId);
333  return attachment.get();
334 }
335 
341 {
342  if(m_firstElement && m_segmentCount == 1) {
343  if(const EbmlElement *segmentElement = m_firstElement->siblingById(MatroskaIds::Segment, true)) {
344  for(const EbmlElement *childElement = segmentElement->firstChild(); childElement; childElement = childElement->nextSibling()) {
345  if(childElement->id() == elementId) {
347  } else if(childElement->id() == MatroskaIds::Cluster) {
348  for(const auto &seekInfo : m_seekInfos) {
349  for(const auto &info : seekInfo->info()) {
350  if(info.first == elementId) {
352  }
353  }
354  }
355  return ElementPosition::Keep;
356  }
357  }
358  }
359  }
360  return ElementPosition::Keep;
361 }
362 
364 {
366 }
367 
369 {
371 }
372 
374 {
375  static const string context("parsing header of Matroska container");
376  // reset old results
377  m_firstElement = make_unique<EbmlElement>(*this, startOffset());
378  m_additionalElements.clear();
379  m_tracksElements.clear();
380  m_segmentInfoElements.clear();
381  m_tagsElements.clear();
382  m_seekInfos.clear();
383  m_segmentCount = 0;
384  uint64 currentOffset = 0;
385  vector<MatroskaSeekInfo>::size_type seekInfosIndex = 0;
386  // loop through all top level elements
387  for(EbmlElement *topLevelElement = m_firstElement.get(); topLevelElement; topLevelElement = topLevelElement->nextSibling()) {
388  try {
389  topLevelElement->parse();
390  switch(topLevelElement->id()) {
391  case EbmlIds::Header:
392  for(EbmlElement *subElement = topLevelElement->firstChild(); subElement; subElement = subElement->nextSibling()) {
393  try {
394  subElement->parse();
395  switch(subElement->id()) {
396  case EbmlIds::Version:
397  m_version = subElement->readUInteger();
398  break;
400  m_readVersion = subElement->readUInteger();
401  break;
402  case EbmlIds::DocType:
403  m_doctype = subElement->readString();
404  break;
406  m_doctypeVersion = subElement->readUInteger();
407  break;
409  m_doctypeReadVersion = subElement->readUInteger();
410  break;
412  m_maxIdLength = subElement->readUInteger();
413  if(m_maxIdLength > EbmlElement::maximumIdLengthSupported()) {
414  addNotification(NotificationType::Critical, "Maximum EBML element ID length greather than "
415  % numberToString<uint32>(EbmlElement::maximumIdLengthSupported())
416  + " bytes is not supported.", context);
417  throw InvalidDataException();
418  }
419  break;
421  m_maxSizeLength = subElement->readUInteger();
422  if(m_maxSizeLength > EbmlElement::maximumSizeLengthSupported()) {
423  addNotification(NotificationType::Critical, "Maximum EBML element size length greather than "
424  % numberToString<uint32>(EbmlElement::maximumSizeLengthSupported())
425  + " bytes is not supported.", context);
426  throw InvalidDataException();
427  }
428  break;
429  }
430  addNotifications(*subElement);
431  } catch(const Failure &) {
432  addNotifications(*subElement);
433  addNotification(NotificationType::Critical, "Unable to parse all childs of EBML header.", context);
434  break;
435  }
436  }
437  break;
439  ++m_segmentCount;
440  for(EbmlElement *subElement = topLevelElement->firstChild(); subElement; subElement = subElement->nextSibling()) {
441  try {
442  subElement->parse();
443  switch(subElement->id()) {
445  m_seekInfos.emplace_back(make_unique<MatroskaSeekInfo>());
446  m_seekInfos.back()->parse(subElement);
447  addNotifications(*m_seekInfos.back());
448  break;
449  case MatroskaIds::Tracks:
450  if(excludesOffset(m_tracksElements, subElement->startOffset())) {
451  m_tracksElements.push_back(subElement);
452  }
453  break;
455  if(excludesOffset(m_segmentInfoElements, subElement->startOffset())) {
456  m_segmentInfoElements.push_back(subElement);
457  }
458  break;
459  case MatroskaIds::Tags:
460  if(excludesOffset(m_tagsElements, subElement->startOffset())) {
461  m_tagsElements.push_back(subElement);
462  }
463  break;
465  if(excludesOffset(m_chaptersElements, subElement->startOffset())) {
466  m_chaptersElements.push_back(subElement);
467  }
468  break;
470  if(excludesOffset(m_attachmentsElements, subElement->startOffset())) {
471  m_attachmentsElements.push_back(subElement);
472  }
473  break;
475  // cluster reached
476  // stop here if all relevant information has been gathered
477  for(auto i = m_seekInfos.cbegin() + seekInfosIndex, end = m_seekInfos.cend(); i != end; ++i, ++seekInfosIndex) {
478  for(const auto &infoPair : (*i)->info()) {
479  uint64 offset = currentOffset + topLevelElement->dataOffset() + infoPair.second;
480  if(offset >= fileInfo().size()) {
481  addNotification(NotificationType::Critical, "Offset (" % numberToString(offset) + ") denoted by \"SeekHead\" element is invalid.", context);
482  } else {
483  auto element = make_unique<EbmlElement>(*this, offset);
484  try {
485  element->parse();
486  if(element->id() != infoPair.first) {
487  addNotification(NotificationType::Critical, "ID of element " % element->idToString() % " at " % numberToString(offset) % " does not match the ID denoted in the \"SeekHead\" element (0x" % numberToString(infoPair.first, 16) + ").", context);
488  }
489  switch(element->id()) {
491  if(excludesOffset(m_segmentInfoElements, offset)) {
492  m_additionalElements.emplace_back(move(element));
493  m_segmentInfoElements.emplace_back(m_additionalElements.back().get());
494  }
495  break;
496  case MatroskaIds::Tracks:
497  if(excludesOffset(m_tracksElements, offset)) {
498  m_additionalElements.emplace_back(move(element));
499  m_tracksElements.emplace_back(m_additionalElements.back().get());
500  }
501  break;
502  case MatroskaIds::Tags:
503  if(excludesOffset(m_tagsElements, offset)) {
504  m_additionalElements.emplace_back(move(element));
505  m_tagsElements.emplace_back(m_additionalElements.back().get());
506  }
507  break;
509  if(excludesOffset(m_chaptersElements, offset)) {
510  m_additionalElements.emplace_back(move(element));
511  m_chaptersElements.emplace_back(m_additionalElements.back().get());
512  }
513  break;
515  if(excludesOffset(m_attachmentsElements, offset)) {
516  m_additionalElements.emplace_back(move(element));
517  m_attachmentsElements.emplace_back(m_additionalElements.back().get());
518  }
519  break;
520  default:
521  ;
522  }
523  } catch(const Failure &) {
524  addNotification(NotificationType::Critical, "Can not parse element at " % numberToString(offset) + " (denoted using \"SeekHead\" element).", context);
525  }
526  }
527  }
528  }
529  // not checking if m_tagsElements is empty avoids long parsing times when loading big files
530  // but also has the disadvantage that the parser relies on the presence of a SeekHead element
531  // (which is not mandatory) to detect tags at the end of the segment
532  if(((!m_tracksElements.empty() && !m_tagsElements.empty()) || fileInfo().size() > m_maxFullParseSize) && !m_segmentInfoElements.empty()) {
533  goto finish;
534  }
535  break;
536  }
537  addNotifications(*subElement);
538  } catch(const Failure &) {
539  addNotifications(*subElement);
540  addNotification(NotificationType::Critical, "Unable to parse all childs of \"Segment\"-element.", context);
541  break;
542  }
543  }
544  currentOffset += topLevelElement->totalSize();
545  break;
546  default:
547  ;
548  }
549  addNotifications(*topLevelElement);
550  } catch(const Failure &) {
551  addNotifications(*topLevelElement);
552  addNotification(NotificationType::Critical, "Unable to parse top-level element at " % numberToString(topLevelElement->startOffset()) + ".", context);
553  break;
554  }
555  }
556  // finally parse the "Info"-element and fetch "EditionEntry"-elements
557 finish:
558  try {
559  parseSegmentInfo();
560  } catch(const Failure &) {
561  addNotification(NotificationType::Critical, "Unable to parse EBML (segment) \"Info\"-element.", context);
562  }
563 }
564 
574 void MatroskaContainer::parseSegmentInfo()
575 {
576  if(m_segmentInfoElements.empty()) {
577  throw NoDataFoundException();
578  }
579  m_duration = TimeSpan();
580  for(EbmlElement *element : m_segmentInfoElements) {
581  element->parse();
582  EbmlElement *subElement = element->firstChild();
583  float64 rawDuration = 0.0;
584  uint64 timeScale = 0;
585  bool hasTitle = false;
586  while(subElement) {
587  subElement->parse();
588  switch(subElement->id()) {
589  case MatroskaIds::Title:
590  m_titles.emplace_back(subElement->readString());
591  hasTitle = true;
592  break;
594  rawDuration = subElement->readFloat();
595  break;
597  timeScale = subElement->readUInteger();
598  break;
599  }
600  subElement = subElement->nextSibling();
601  }
602  if(!hasTitle) {
603  // add empty string as title for segment if no
604  // "Title"-element has been specified
605  m_titles.emplace_back();
606  }
607  if(rawDuration > 0.0 && timeScale > 0) {
608  m_duration += TimeSpan::fromSeconds(rawDuration * timeScale / 1000000000);
609  }
610  }
611 }
612 
614 {
615  static const string context("parsing tags of Matroska container");
616  for(EbmlElement *element : m_tagsElements) {
617  try {
618  element->parse();
619  for(EbmlElement *subElement = element->firstChild(); subElement; subElement = subElement->nextSibling()) {
620  subElement->parse();
621  switch(subElement->id()) {
622  case MatroskaIds::Tag:
623  m_tags.emplace_back(make_unique<MatroskaTag>());
624  try {
625  m_tags.back()->parse(*subElement);
626  } catch(NoDataFoundException &) {
627  m_tags.pop_back();
628  } catch(const Failure &) {
629  addNotification(NotificationType::Critical, "Unable to parse tag " % ConversionUtilities::numberToString(m_tags.size()) + ".", context);
630  }
631  break;
632  case EbmlIds::Crc32:
633  case EbmlIds::Void:
634  break;
635  default:
636  addNotification(NotificationType::Warning, "\"Tags\"-element contains unknown child. It will be ignored.", context);
637  }
638  }
639  } catch(const Failure &) {
640  addNotification(NotificationType::Critical, "Element structure seems to be invalid.", context);
641  throw;
642  }
643  }
644 }
645 
647 {
649  static const string context("parsing tracks of Matroska container");
650  for(EbmlElement *element : m_tracksElements) {
651  try {
652  element->parse();
653  for(EbmlElement *subElement = element->firstChild(); subElement; subElement = subElement->nextSibling()) {
654  subElement->parse();
655  switch(subElement->id()) {
657  m_tracks.emplace_back(make_unique<MatroskaTrack>(*subElement));
658  try {
659  m_tracks.back()->parseHeader();
660  } catch(const NoDataFoundException &) {
661  m_tracks.pop_back();
662  } catch(const Failure &) {
663  addNotification(NotificationType::Critical, "Unable to parse track " % ConversionUtilities::numberToString(m_tracks.size()) + ".", context);
664  }
665  break;
666  case EbmlIds::Crc32:
667  case EbmlIds::Void:
668  break;
669  default:
670  addNotification(NotificationType::Warning, "\"Tracks\"-element contains unknown child element \"" % subElement->idToString() + "\". It will be ignored.", context);
671  }
672  }
673  } catch(const Failure &) {
674  addNotification(NotificationType::Critical, "Element structure seems to be invalid.", context);
675  throw;
676  }
677  }
678 }
679 
681 {
683  static const string context("parsing editions/chapters of Matroska container");
684  for(EbmlElement *element : m_chaptersElements) {
685  try {
686  element->parse();
687  for(EbmlElement *subElement = element->firstChild(); subElement; subElement = subElement->nextSibling()) {
688  subElement->parse();
689  switch(subElement->id()) {
691  m_editionEntries.emplace_back(make_unique<MatroskaEditionEntry>(subElement));
692  try {
693  m_editionEntries.back()->parseNested();
694  } catch(const NoDataFoundException &) {
695  m_editionEntries.pop_back();
696  } catch(const Failure &) {
697  addNotification(NotificationType::Critical, "Unable to parse edition entry " % ConversionUtilities::numberToString(m_editionEntries.size()) + ".", context);
698  }
699  break;
700  case EbmlIds::Crc32:
701  case EbmlIds::Void:
702  break;
703  default:
704  addNotification(NotificationType::Warning, "\"Chapters\"-element contains unknown child element \"" % subElement->idToString() + "\". It will be ignored.", context);
705  }
706  }
707  } catch(const Failure &) {
708  addNotification(NotificationType::Critical, "Element structure seems to be invalid.", context);
709  throw;
710  }
711  }
712 }
713 
715 {
717  static const string context("parsing attachments of Matroska container");
718  for(EbmlElement *element : m_attachmentsElements) {
719  try {
720  element->parse();
721  for(EbmlElement *subElement = element->firstChild(); subElement; subElement = subElement->nextSibling()) {
722  subElement->parse();
723  switch(subElement->id()) {
725  m_attachments.emplace_back(make_unique<MatroskaAttachment>());
726  try {
727  m_attachments.back()->parse(subElement);
728  } catch(const NoDataFoundException &) {
729  m_attachments.pop_back();
730  } catch(const Failure &) {
731  addNotification(NotificationType::Critical, "Unable to parse attached file " % ConversionUtilities::numberToString(m_attachments.size()) + ".", context);
732  }
733  break;
734  case EbmlIds::Crc32:
735  case EbmlIds::Void:
736  break;
737  default:
738  addNotification(NotificationType::Warning, "\"Attachments\"-element contains unknown child element \"" % subElement->idToString() + "\". It will be ignored.", context);
739  }
740  }
741  } catch(const Failure &) {
742  addNotification(NotificationType::Critical, "Element structure seems to be invalid.", context);
743  throw;
744  }
745  }
746 }
747 
750 {
753  hasCrc32(false),
754  cuesElement(nullptr),
755  infoDataSize(0),
756  firstClusterElement(nullptr),
757  clusterEndOffset(0),
758  startOffset(0),
759  newPadding(0),
760  sizeDenotationLength(0),
761  totalDataSize(0),
762  totalSize(0),
763  newDataOffset(0)
764  {}
765 
767  bool hasCrc32;
775  uint64 infoDataSize;
777  vector<uint64> clusterSizes;
783  uint64 startOffset;
785  uint64 newPadding;
791  uint64 totalSize;
794 };
795 
797 {
798  // set initial status
800  static const string context("making Matroska container");
801  updateStatus("Calculating element sizes ...");
802 
803  // basic validation of original file
804  if(!isHeaderParsed()) {
805  addNotification(NotificationType::Critical, "The header has not been parsed yet.", context);
806  throw InvalidDataException();
807  }
808 
809  // define variables for parsing the elements of the original file
810  EbmlElement *level0Element = firstElement();
811  if(!level0Element) {
812  addNotification(NotificationType::Critical, "No EBML elements could be found.", context);
813  throw InvalidDataException();
814  }
815  EbmlElement *level1Element, *level2Element;
816 
817  // define variables needed for precalculation of "Tags"- and "Attachments"-element
818  vector<MatroskaTagMaker> tagMaker;
819  uint64 tagElementsSize = 0;
820  uint64 tagsSize;
821  vector<MatroskaAttachmentMaker> attachmentMaker;
822  uint64 attachedFileElementsSize = 0;
823  uint64 attachmentsSize;
824 
825  // define variables to store sizes, offsets and other information required to make a header and "Segment"-elements
826  // current segment index
827  unsigned int segmentIndex = 0;
828  // segment specific data
829  vector<SegmentData> segmentData;
830  // offset of the segment which is currently written / offset of "Cues"-element in segment
831  uint64 offset;
832  // current total offset (including EBML header)
833  uint64 totalOffset;
834  // current write offset (used to calculate positions)
835  uint64 currentPosition = 0;
836  // holds the offsets of all CRC-32 elements and the length of the enclosing block
837  vector<tuple<uint64, uint64> > crc32Offsets;
838  // size length used to make size denotations
839  byte sizeLength;
840  // sizes and offsets for cluster calculation
841  uint64 clusterSize, clusterReadSize, clusterReadOffset;
842 
843  // define variables needed to manage file layout
844  // -> use the preferred tag position by default (might be changed later if not forced)
845  ElementPosition newTagPos = fileInfo().tagPosition();
846  // -> current tag position (determined later)
847  ElementPosition currentTagPos = ElementPosition::Keep;
848  // -> use the preferred cue position by default (might be changed later if not forced)
849  ElementPosition newCuesPos = fileInfo().indexPosition();
850  // --> current cue position (determined later)
851  ElementPosition currentCuesPos = ElementPosition::Keep;
852  // -> index of the last segment
853  unsigned int lastSegmentIndex = static_cast<unsigned int>(-1);
854  // -> holds new padding
855  uint64 newPadding;
856  // -> whether rewrite is required (always required when forced to rewrite)
857  bool rewriteRequired = fileInfo().isForcingRewrite() || !fileInfo().saveFilePath().empty();
858 
859  // calculate EBML header size
860  // -> sub element ID sizes
861  uint64 ebmlHeaderDataSize = 2 * 7;
862  // -> content and size denotation length of numeric sub elements
863  for(auto headerValue : initializer_list<uint64>{m_version, m_readVersion, m_maxIdLength, m_maxSizeLength, m_doctypeVersion, m_doctypeReadVersion}) {
864  ebmlHeaderDataSize += sizeLength = EbmlElement::calculateUIntegerLength(headerValue);
865  ebmlHeaderDataSize += EbmlElement::calculateSizeDenotationLength(sizeLength);
866  }
867  // -> content and size denotation length of string sub elements
868  ebmlHeaderDataSize += m_doctype.size();
869  ebmlHeaderDataSize += EbmlElement::calculateSizeDenotationLength(m_doctype.size());
870  uint64 ebmlHeaderSize = 4 + EbmlElement::calculateSizeDenotationLength(ebmlHeaderDataSize) + ebmlHeaderDataSize;
871 
872  try {
873  // calculate size of "Tags"-element
874  for(auto &tag : tags()) {
876  try {
877  tagMaker.emplace_back(tag->prepareMaking());
878  if(tagMaker.back().requiredSize() > 3) {
879  // a tag of 3 bytes size is empty and can be skipped
880  tagElementsSize += tagMaker.back().requiredSize();
881  }
882  } catch(const Failure &) {
883  // nothing to do because notifications will be added anyways
884  }
886  }
887  tagsSize = tagElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(tagElementsSize) + tagElementsSize : 0;
888 
889  // calculate size of "Attachments"-element
890  for(auto &attachment : m_attachments) {
891  if(!attachment->isIgnored()) {
893  try {
894  attachmentMaker.emplace_back(attachment->prepareMaking());
895  if(attachmentMaker.back().requiredSize() > 3) {
896  // an attachment of 3 bytes size is empty and can be skipped
897  attachedFileElementsSize += attachmentMaker.back().requiredSize();
898  }
899  } catch(const Failure &) {
900  // nothing to do because notifications will be added anyways
901  }
903  }
904  }
905  attachmentsSize = attachedFileElementsSize ? 4 + EbmlElement::calculateSizeDenotationLength(attachedFileElementsSize) + attachedFileElementsSize : 0;
906 
907  // inspect layout of original file
908  // - number of segments
909  // - position of tags relative to the media data
910  try {
911  for(bool firstClusterFound = false, firstTagFound = false; level0Element; level0Element = level0Element->nextSibling()) {
912  level0Element->parse();
913  switch(level0Element->id()) {
915  ++lastSegmentIndex;
916  for(level1Element = level0Element->firstChild(); level1Element && !firstClusterFound && !firstTagFound; level1Element = level1Element->nextSibling()) {
917  level1Element->parse();
918  switch(level1Element->id()) {
919  case MatroskaIds::Tags:
921  firstTagFound = true;
922  break;
924  firstClusterFound = true;
925  }
926  }
927  if(firstTagFound) {
928  currentTagPos = ElementPosition::BeforeData;
929  } else if(firstClusterFound) {
930  currentTagPos = ElementPosition::AfterData;
931  }
932  }
933  }
934 
935  // now the number of segments is known -> allocate segment specific data
936  segmentData.resize(lastSegmentIndex + 1);
937 
938  // now the current tag/cue position might be known
939  if(newTagPos == ElementPosition::Keep) {
940  if((newTagPos = currentTagPos) == ElementPosition::Keep) {
941  newTagPos = ElementPosition::BeforeData;
942  }
943  }
944 
945  } catch(const Failure &) {
946  addNotification(NotificationType::Critical, "Unable to parse content in top-level element at " % numberToString(level0Element->startOffset()) + " of original file.", context);
947  throw;
948  }
949 
950 calculateSegmentData:
951  // define variables to store sizes, offsets and other information required to make a header and "Segment"-elements
952  // -> current "pretent" write offset
953  uint64 currentOffset = ebmlHeaderSize;
954  // -> current read offset (used to calculate positions)
955  uint64 readOffset = 0;
956  // -> index of current element during iteration
957  unsigned int index;
958 
959  // if rewriting is required always use the preferred tag/cue position
960  if(rewriteRequired) {
961  newTagPos = fileInfo().tagPosition();
962  if(newTagPos == ElementPosition::Keep) {
963  if((newTagPos = currentTagPos) == ElementPosition::Keep) {
964  newTagPos = ElementPosition::BeforeData;
965  }
966  }
967  newCuesPos = fileInfo().indexPosition();
968  }
969 
970  // calculate sizes and other information required to make segments
971  updateStatus("Calculating segment data ...", 0.0);
972  for(level0Element = firstElement(), currentPosition = newPadding = segmentIndex = 0; level0Element; level0Element = level0Element->nextSibling()) {
973  switch(level0Element->id()) {
974  case EbmlIds::Header:
975  // header size has already been calculated
976  break;
977 
978  case EbmlIds::Void:
979  case EbmlIds::Crc32:
980  // level 0 "Void"- and "Checksum"-elements are omitted
981  break;
982 
983  case MatroskaIds::Segment: {
984  // get reference to the current segment data instance
985  SegmentData &segment = segmentData[segmentIndex];
986 
987  // parse original "Cues"-element (if present)
988  if(!segment.cuesElement) {
989  if((segment.cuesElement = level0Element->childById(MatroskaIds::Cues))) {
990  try {
991  segment.cuesUpdater.parse(segment.cuesElement);
992  } catch(const Failure &) {
993  addNotifications(segment.cuesUpdater);
994  throw;
995  }
996  addNotifications(segment.cuesUpdater);
997  }
998  }
999 
1000  // get first "Cluster"-element
1001  if(!segment.firstClusterElement) {
1002  segment.firstClusterElement = level0Element->childById(MatroskaIds::Cluster);
1003  }
1004 
1005  // determine current/new cue position
1006  if(segment.cuesElement && segment.firstClusterElement) {
1008  if(newCuesPos == ElementPosition::Keep) {
1009  newCuesPos = currentCuesPos;
1010  }
1011  } else if(newCuesPos == ElementPosition::Keep) {
1012  newCuesPos = ElementPosition::BeforeData;
1013  }
1014 
1015  // set start offset of the segment in the new file
1016  segment.startOffset = currentOffset;
1017 
1018  // check whether the segment has a CRC-32 element
1019  segment.hasCrc32 = level0Element->firstChild() && level0Element->firstChild()->id() == EbmlIds::Crc32;
1020 
1021  // precalculate the size of the segment
1022 calculateSegmentSize:
1023 
1024  // pretent writing "CRC-32"-element (which either present and 6 byte long or omitted)
1025  segment.totalDataSize = segment.hasCrc32 ? 6 : 0;
1026 
1027  // pretend writing "SeekHead"-element
1028  segment.totalDataSize += segment.seekInfo.actualSize();
1029 
1030  // pretend writing "SegmentInfo"-element
1031  for(level1Element = level0Element->childById(MatroskaIds::SegmentInfo), index = 0; level1Element; level1Element = level1Element->siblingById(MatroskaIds::SegmentInfo), ++index) {
1032  // update offset in "SeekHead"-element
1033  if(segment.seekInfo.push(index, MatroskaIds::SegmentInfo, currentPosition + segment.totalDataSize)) {
1034  goto calculateSegmentSize;
1035  } else {
1036  // add size of "SegmentInfo"-element
1037  // -> size of "MuxingApp"- and "WritingApp"-element
1038  segment.infoDataSize = 2 * appInfoElementTotalSize;
1039  // -> add size of "Title"-element
1040  if(segmentIndex < m_titles.size()) {
1041  const auto &title = m_titles[segmentIndex];
1042  if(!title.empty()) {
1043  segment.infoDataSize += 2 + EbmlElement::calculateSizeDenotationLength(title.size()) + title.size();
1044  }
1045  }
1046  // -> add size of other childs
1047  for(level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1048  level2Element->parse();
1049  switch(level2Element->id()) {
1050  case EbmlIds::Void: // skipped
1051  case EbmlIds::Crc32: // skipped
1052  case MatroskaIds::Title: // calculated separately
1053  case MatroskaIds::MuxingApp: // calculated separately
1054  case MatroskaIds::WrittingApp: // calculated separately
1055  break;
1056  default:
1057  level2Element->makeBuffer();
1058  segment.infoDataSize += level2Element->totalSize();
1059  }
1060  }
1061  // -> calculate total size
1063  }
1064  }
1065 
1066  // pretend writing "Tracks"- and "Chapters"-element
1067  for(const auto id : initializer_list<EbmlElement::identifierType>{MatroskaIds::Tracks, MatroskaIds::Chapters}) {
1068  for(level1Element = level0Element->childById(id), index = 0; level1Element; level1Element = level1Element->siblingById(id), ++index) {
1069  // update offset in "SeekHead"-element
1070  if(segment.seekInfo.push(index, id, currentPosition + segment.totalDataSize)) {
1071  goto calculateSegmentSize;
1072  } else {
1073  // add size of element
1074  level1Element->makeBuffer();
1075  segment.totalDataSize += level1Element->totalSize();
1076  }
1077  }
1078  }
1079 
1080  // "Tags"- and "Attachments"-element are written in either the first or the last segment
1081  // and either before "Cues"- and "Cluster"-elements or after these elements
1082  // depending on the desired tag position (at the front/at the end)
1083  if(newTagPos == ElementPosition::BeforeData && segmentIndex == 0) {
1084  // pretend writing "Tags"-element
1085  if(tagsSize) {
1086  // update offsets in "SeekHead"-element
1087  if(segment.seekInfo.push(0, MatroskaIds::Tags, currentPosition + segment.totalDataSize)) {
1088  goto calculateSegmentSize;
1089  } else {
1090  // add size of "Tags"-element
1091  segment.totalDataSize += tagsSize;
1092  }
1093  }
1094  // pretend writing "Attachments"-element
1095  if(attachmentsSize) {
1096  // update offsets in "SeekHead"-element
1097  if(segment.seekInfo.push(0, MatroskaIds::Attachments, currentPosition + segment.totalDataSize)) {
1098  goto calculateSegmentSize;
1099  } else {
1100  // add size of "Attachments"-element
1101  segment.totalDataSize += attachmentsSize;
1102  }
1103  }
1104  }
1105 
1106  offset = segment.totalDataSize; // save current offset (offset before "Cues"-element)
1107 
1108  // pretend writing "Cues"-element
1109  if(newCuesPos == ElementPosition::BeforeData && segment.cuesElement) {
1110  // update offset of "Cues"-element in "SeekHead"-element
1111  if(segment.seekInfo.push(0, MatroskaIds::Cues, currentPosition + segment.totalDataSize)) {
1112  goto calculateSegmentSize;
1113  } else {
1114  // add size of "Cues"-element
1115 addCuesElementSize:
1116  segment.totalDataSize += segment.cuesUpdater.totalSize();
1117  }
1118  }
1119 
1120  // decided whether it is necessary to rewrite the entire file (if not already rewriting)
1121  if(!rewriteRequired) {
1122  // -> find first "Cluster"-element
1123  if((level1Element = segment.firstClusterElement)) {
1124  // there is at least one "Cluster"-element to be written
1125  //if(level1Element->startOffset() == currentFirstClusterOffset) {
1126  // just before the first "Cluster"-element
1127  // -> calculate total offset (excluding size denotation and incomplete index)
1128  totalOffset = currentOffset + 4 + segment.totalDataSize;
1129 
1130  if(totalOffset <= segment.firstClusterElement->startOffset()) {
1131  // the padding might be big enough, but
1132  // - the segment might become bigger (subsequent tags and attachments)
1133  // - the header size hasn't been taken into account yet
1134  // - seek information for first cluster and subsequent tags and attachments hasn't been taken into account
1135 
1136  // assume the size denotation length doesn't change -> use length from original file
1137  if(level0Element->headerSize() <= 4 || level0Element->headerSize() > 12) {
1138  // validate original header size
1139  addNotification(NotificationType::Critical, "Header size of \"Segment\"-element from original file is invalid.", context);
1140  throw InvalidDataException();
1141  }
1142  segment.sizeDenotationLength = level0Element->headerSize() - 4;
1143 
1144 nonRewriteCalculations:
1145  // pretend writing "Cluster"-elements assuming there is no rewrite required
1146  // -> update offset in "SeakHead"-element
1147  if(segment.seekInfo.push(0, MatroskaIds::Cluster, level1Element->startOffset() - 4 - segment.sizeDenotationLength - ebmlHeaderSize)) {
1148  goto calculateSegmentSize;
1149  }
1150  // -> update offset of "Cluster"-element in "Cues"-element and get end offset of last "Cluster"-element
1151  for(; level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster)) {
1152  clusterReadOffset = level1Element->startOffset() - level0Element->dataOffset() + readOffset;
1153  segment.clusterEndOffset = level1Element->endOffset();
1154  if(segment.cuesElement && segment.cuesUpdater.updateOffsets(clusterReadOffset, level1Element->startOffset() - 4 - segment.sizeDenotationLength - ebmlHeaderSize) && newCuesPos == ElementPosition::BeforeData) {
1155  segment.totalDataSize = offset;
1156  goto addCuesElementSize;
1157  }
1158  }
1159  segment.totalDataSize = segment.clusterEndOffset - currentOffset - 4 - segment.sizeDenotationLength;
1160 
1161  // pretend writing "Cues"-element
1162  if(newCuesPos == ElementPosition::AfterData && segment.cuesElement) {
1163  // update offset of "Cues"-element in "SeekHead"-element
1164  if(segment.seekInfo.push(0, MatroskaIds::Cues, currentPosition + segment.totalDataSize)) {
1165  goto calculateSegmentSize;
1166  } else {
1167  // add size of "Cues"-element
1168  segment.totalDataSize += segment.cuesUpdater.totalSize();
1169  }
1170  }
1171 
1172  if(newTagPos == ElementPosition::AfterData && segmentIndex == lastSegmentIndex) {
1173  // pretend writing "Tags"-element
1174  if(tagsSize) {
1175  // update offsets in "SeekHead"-element
1176  if(segment.seekInfo.push(0, MatroskaIds::Tags, currentPosition + segment.totalDataSize)) {
1177  goto calculateSegmentSize;
1178  } else {
1179  // add size of "Tags"-element
1180  segment.totalDataSize += tagsSize;
1181  }
1182  }
1183  // pretend writing "Attachments"-element
1184  if(attachmentsSize) {
1185  // update offsets in "SeekHead"-element
1186  if(segment.seekInfo.push(0, MatroskaIds::Attachments, currentPosition + segment.totalDataSize)) {
1187  goto calculateSegmentSize;
1188  } else {
1189  // add size of "Attachments"-element
1190  segment.totalDataSize += attachmentsSize;
1191  }
1192  }
1193  }
1194 
1195  // calculate total offset again (taking everything into account)
1196  // -> check whether assumed size denotation was correct
1197  if(segment.sizeDenotationLength != (sizeLength = EbmlElement::calculateSizeDenotationLength(segment.totalDataSize))) {
1198  // assumption was wrong -> recalculate with new length
1199  segment.sizeDenotationLength = sizeLength;
1200  level1Element = segment.firstClusterElement;
1201  goto nonRewriteCalculations;
1202  }
1203 
1204  totalOffset = currentOffset + 4 + sizeLength + offset;
1205  // offset does not include size of "Cues"-element
1206  if(newCuesPos == ElementPosition::BeforeData) {
1207  totalOffset += segment.cuesUpdater.totalSize();
1208  }
1209  if(totalOffset <= segment.firstClusterElement->startOffset()) {
1210  // calculate new padding
1211  if(segment.newPadding != 1) {
1212  // "Void"-element is at least 2 byte long -> can't add 1 byte padding
1213  newPadding += (segment.newPadding = segment.firstClusterElement->startOffset() - totalOffset);
1214  } else {
1215  rewriteRequired = true;
1216  }
1217  } else {
1218  rewriteRequired = true;
1219  }
1220  } else {
1221  rewriteRequired = true;
1222  }
1223  //} else {
1224  // first "Cluster"-element in the "Segment"-element but not the first "Cluster"-element in the file
1225  // TODO / nothing to do?
1226  //}
1227 
1228  } else {
1229  // there are no "Cluster"-elements in the current "Segment"-element
1230  // TODO / nothing to do?
1231  }
1232 
1233  if(rewriteRequired) {
1234  if(newTagPos != ElementPosition::AfterData && (!fileInfo().forceTagPosition() || (fileInfo().tagPosition() == ElementPosition::Keep && currentTagPos == ElementPosition::Keep))) {
1235  // rewriting might be avoided by writing the tags at the end
1236  newTagPos = ElementPosition::AfterData;
1237  rewriteRequired = false;
1238  } else if(newCuesPos != ElementPosition::AfterData && (!fileInfo().forceIndexPosition() || (fileInfo().indexPosition() == ElementPosition::Keep && currentCuesPos == ElementPosition::Keep))) {
1239  // rewriting might be avoided by writing the cues at the end
1240  newCuesPos = ElementPosition::AfterData;
1241  rewriteRequired = false;
1242  }
1243  // do calculations again for rewriting / changed element order
1244  goto calculateSegmentData;
1245  }
1246  } else {
1247  // if rewrite is required pretend writing the remaining elements to compute total segment size
1248 
1249  // pretend writing "Void"-element (only if there is at least one "Cluster"-element in the segment)
1250  if(!segmentIndex && rewriteRequired && (level1Element = level0Element->childById(MatroskaIds::Cluster))) {
1251  // simply use the preferred padding
1252  segment.totalDataSize += (segment.newPadding = newPadding = fileInfo().preferredPadding());
1253  }
1254 
1255  // pretend writing "Cluster"-element
1256  segment.clusterSizes.clear();
1257  for(index = 0; level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster), ++index) {
1258  // update offset of "Cluster"-element in "Cues"-element
1259  clusterReadOffset = level1Element->startOffset() - level0Element->dataOffset() + readOffset;
1260  if(segment.cuesElement && segment.cuesUpdater.updateOffsets(clusterReadOffset, currentPosition + segment.totalDataSize) && newCuesPos == ElementPosition::BeforeData) {
1261  segment.totalDataSize = offset; // reset element size to previously saved offset of "Cues"-element
1262  goto addCuesElementSize;
1263  } else {
1264  if(index == 0 && segment.seekInfo.push(index, MatroskaIds::Cluster, currentPosition + segment.totalDataSize)) {
1265  goto calculateSegmentSize;
1266  } else {
1267  // add size of "Cluster"-element
1268  clusterSize = clusterReadSize = 0;
1269  for(level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1270  level2Element->parse();
1271  if(segment.cuesElement && segment.cuesUpdater.updateRelativeOffsets(clusterReadOffset, clusterReadSize, clusterSize) && newCuesPos == ElementPosition::BeforeData) {
1272  segment.totalDataSize = offset;
1273  goto addCuesElementSize;
1274  }
1275  switch(level2Element->id()) {
1276  case EbmlIds::Void:
1277  case EbmlIds::Crc32:
1278  break;
1279  case MatroskaIds::Position:
1280  clusterSize += 1 + 1 + EbmlElement::calculateUIntegerLength(currentPosition + segment.totalDataSize);
1281  break;
1282  default:
1283  clusterSize += level2Element->totalSize();
1284  }
1285  clusterReadSize += level2Element->totalSize();
1286  }
1287  segment.clusterSizes.push_back(clusterSize);
1288  segment.totalDataSize += 4 + EbmlElement::calculateSizeDenotationLength(clusterSize) + clusterSize;
1289  }
1290  }
1291  }
1292 
1293  // pretend writing "Cues"-element
1294  if(newCuesPos == ElementPosition::AfterData && segment.cuesElement) {
1295  // update offset of "Cues"-element in "SeekHead"-element
1296  if(segment.seekInfo.push(0, MatroskaIds::Cues, currentPosition + segment.totalDataSize)) {
1297  goto calculateSegmentSize;
1298  } else {
1299  // add size of "Cues"-element
1300  segment.totalDataSize += segment.cuesUpdater.totalSize();
1301  }
1302  }
1303 
1304  // "Tags"- and "Attachments"-element are written in either the first or the last segment
1305  // and either before "Cues"- and "Cluster"-elements or after these elements
1306  // depending on the desired tag position (at the front/at the end)
1307  if(newTagPos == ElementPosition::AfterData && segmentIndex == lastSegmentIndex) {
1308  // pretend writing "Tags"-element
1309  if(tagsSize) {
1310  // update offsets in "SeekHead"-element
1311  if(segment.seekInfo.push(0, MatroskaIds::Tags, currentPosition + segment.totalDataSize)) {
1312  goto calculateSegmentSize;
1313  } else {
1314  // add size of "Tags"-element
1315  segment.totalDataSize += tagsSize;
1316  }
1317  }
1318  // pretend writing "Attachments"-element
1319  if(attachmentsSize) {
1320  // update offsets in "SeekHead"-element
1321  if(segment.seekInfo.push(0, MatroskaIds::Attachments, currentPosition + segment.totalDataSize)) {
1322  goto calculateSegmentSize;
1323  } else {
1324  // add size of "Attachments"-element
1325  segment.totalDataSize += attachmentsSize;
1326  }
1327  }
1328  }
1329  }
1330 
1331  // increase the current segment index
1332  ++segmentIndex;
1333 
1334  // increase write offsets by the size of the segment which size has just been computed
1336  currentPosition += segment.totalSize;
1337  currentOffset += segment.totalSize;
1338 
1339  // increase the read offset by the size of the segment read from the orignial file
1340  readOffset += level0Element->totalSize();
1341 
1342  break;
1343 
1344  } default:
1345  // just copy any unknown top-level elements
1346  addNotification(NotificationType::Warning, "The top-level element \"" % level0Element->idToString() + "\" of the original file is unknown and will just be copied.", context);
1347  currentOffset += level0Element->totalSize();
1348  readOffset += level0Element->totalSize();
1349  }
1350  }
1351 
1352  if(!rewriteRequired) {
1353  // check whether the new padding is ok according to specifications
1354  if((rewriteRequired = (newPadding > fileInfo().maxPadding() || newPadding < fileInfo().minPadding()))) {
1355  // need to recalculate segment data for rewrite
1356  goto calculateSegmentData;
1357  }
1358  }
1359 
1360  } catch(const Failure &) {
1361  addNotification(NotificationType::Critical, "Parsing the original file failed.", context);
1362  throw;
1363  } catch(...) {
1364  const char *what = catchIoFailure();
1365  addNotification(NotificationType::Critical, "An IO error occured when parsing the original file.", context);
1366  throwIoFailure(what);
1367  }
1368 
1369  if(isAborted()) {
1370  throw OperationAbortedException();
1371  }
1372 
1373  // setup stream(s) for writing
1374  // -> update status
1375  updateStatus("Preparing streams ...");
1376 
1377  // -> define variables needed to handle output stream and backup stream (required when rewriting the file)
1378  string backupPath;
1379  NativeFileStream &outputStream = fileInfo().stream();
1380  NativeFileStream backupStream; // create a stream to open the backup/original file for the case rewriting the file is required
1381  BinaryWriter outputWriter(&outputStream);
1382  char buff[8]; // buffer used to make size denotations
1383 
1384  if(rewriteRequired) {
1385  if(fileInfo().saveFilePath().empty()) {
1386  // move current file to temp dir and reopen it as backupStream, recreate original file
1387  try {
1388  BackupHelper::createBackupFile(fileInfo().path(), backupPath, outputStream, backupStream);
1389  // recreate original file, define buffer variables
1390  outputStream.open(fileInfo().path(), ios_base::out | ios_base::binary | ios_base::trunc);
1391  } catch(...) {
1392  const char *what = catchIoFailure();
1393  addNotification(NotificationType::Critical, "Creation of temporary file (to rewrite the original file) failed.", context);
1394  throwIoFailure(what);
1395  }
1396  } else {
1397  // open the current file as backupStream and create a new outputStream at the specified "save file path"
1398  try {
1399  backupStream.exceptions(ios_base::badbit | ios_base::failbit);
1400  backupStream.open(fileInfo().path(), ios_base::in | ios_base::binary);
1401  fileInfo().close();
1402  outputStream.open(fileInfo().saveFilePath(), ios_base::out | ios_base::binary | ios_base::trunc);
1403  } catch(...) {
1404  const char *what = catchIoFailure();
1405  addNotification(NotificationType::Critical, "Opening streams to write output file failed.", context);
1406  throwIoFailure(what);
1407  }
1408  }
1409 
1410  // set backup stream as associated input stream since we need the original elements to write the new file
1411  setStream(backupStream);
1412 
1413  // TODO: reduce code duplication
1414 
1415  } else { // !rewriteRequired
1416  // buffer currently assigned attachments
1417  for(auto &maker : attachmentMaker) {
1418  maker.bufferCurrentAttachments();
1419  }
1420 
1421  // reopen original file to ensure it is opened for writing
1422  try {
1423  fileInfo().close();
1424  outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1425  } catch(...) {
1426  const char *what = catchIoFailure();
1427  addNotification(NotificationType::Critical, "Opening the file with write permissions failed.", context);
1428  throwIoFailure(what);
1429  }
1430  }
1431 
1432  // start actual writing
1433  try {
1434  // write EBML header
1435  updateStatus("Writing EBML header ...");
1436  outputWriter.writeUInt32BE(EbmlIds::Header);
1437  sizeLength = EbmlElement::makeSizeDenotation(ebmlHeaderDataSize, buff);
1438  outputStream.write(buff, sizeLength);
1441  EbmlElement::makeSimpleElement(outputStream, EbmlIds::MaxIdLength, m_maxIdLength);
1442  EbmlElement::makeSimpleElement(outputStream, EbmlIds::MaxSizeLength, m_maxSizeLength);
1446 
1447  // iterates through all level 0 elements of the original file
1448  for(level0Element = firstElement(), segmentIndex = 0, currentPosition = 0; level0Element; level0Element = level0Element->nextSibling()) {
1449 
1450  // write all level 0 elements of the original file
1451  switch(level0Element->id()) {
1452  case EbmlIds::Header:
1453  // header has already been written -> skip it here
1454  break;
1455 
1456  case EbmlIds::Void:
1457  case EbmlIds::Crc32:
1458  // level 0 "Void"- and "Checksum"-elements are omitted
1459  break;
1460 
1461  case MatroskaIds::Segment: {
1462  // get reference to the current segment data instance
1463  SegmentData &segment = segmentData[segmentIndex];
1464 
1465  // write "Segment"-element actually
1466  updateStatus("Writing segment header ...");
1467  outputWriter.writeUInt32BE(MatroskaIds::Segment);
1468  sizeLength = EbmlElement::makeSizeDenotation(segment.totalDataSize, buff);
1469  outputStream.write(buff, sizeLength);
1470  segment.newDataOffset = offset = outputStream.tellp(); // store segment data offset here
1471 
1472  // write CRC-32 element ...
1473  if(segment.hasCrc32) {
1474  // ... if the original element had a CRC-32 element
1475  *buff = EbmlIds::Crc32;
1476  *(buff + 1) = 0x84; // length denotation: 4 byte
1477  // set the value after writing the element
1478  crc32Offsets.emplace_back(outputStream.tellp(), segment.totalDataSize);
1479  outputStream.write(buff, 6);
1480  }
1481 
1482  // write "SeekHead"-element (except there is no seek information for the current segment)
1484  segment.seekInfo.make(outputStream);
1485  addNotifications(segment.seekInfo);
1486 
1487  // write "SegmentInfo"-element
1488  for(level1Element = level0Element->childById(MatroskaIds::SegmentInfo); level1Element; level1Element = level1Element->siblingById(MatroskaIds::SegmentInfo)) {
1489  // -> write ID and size
1490  outputWriter.writeUInt32BE(MatroskaIds::SegmentInfo);
1491  sizeLength = EbmlElement::makeSizeDenotation(segment.infoDataSize, buff);
1492  outputStream.write(buff, sizeLength);
1493  // -> write childs
1494  for(level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1495  switch(level2Element->id()) {
1496  case EbmlIds::Void: // skipped
1497  case EbmlIds::Crc32: // skipped
1498  case MatroskaIds::Title: // written separately
1499  case MatroskaIds::MuxingApp: // written separately
1500  case MatroskaIds::WrittingApp: // written separately
1501  break;
1502  default:
1503  level2Element->copyBuffer(outputStream);
1504  level2Element->discardBuffer();
1505  //level2Element->copyEntirely(outputStream);
1506  }
1507  }
1508  // -> write "Title"-element
1509  if(segmentIndex < m_titles.size()) {
1510  const auto &title = m_titles[segmentIndex];
1511  if(!title.empty()) {
1513  }
1514  }
1515  // -> write "MuxingApp"- and "WritingApp"-element
1516  EbmlElement::makeSimpleElement(outputStream, MatroskaIds::MuxingApp, appInfo, appInfoElementDataSize);
1517  EbmlElement::makeSimpleElement(outputStream, MatroskaIds::WrittingApp, appInfo, appInfoElementDataSize);
1518  }
1519 
1520  // write "Tracks"- and "Chapters"-element
1521  for(const auto id : initializer_list<EbmlElement::identifierType>{MatroskaIds::Tracks, MatroskaIds::Chapters}) {
1522  for(level1Element = level0Element->childById(id); level1Element; level1Element = level1Element->siblingById(id)) {
1523  level1Element->copyBuffer(outputStream);
1524  level1Element->discardBuffer();
1525  //level1Element->copyEntirely(outputStream);
1526  }
1527  }
1528 
1529  if(newTagPos == ElementPosition::BeforeData && segmentIndex == 0) {
1530  // write "Tags"-element
1531  if(tagsSize) {
1532  outputWriter.writeUInt32BE(MatroskaIds::Tags);
1533  sizeLength = EbmlElement::makeSizeDenotation(tagElementsSize, buff);
1534  outputStream.write(buff, sizeLength);
1535  for(auto &maker : tagMaker) {
1536  maker.make(outputStream);
1537  }
1538  // no need to add notifications; this has been done when creating the make
1539  }
1540  // write "Attachments"-element
1541  if(attachmentsSize) {
1542  outputWriter.writeUInt32BE(MatroskaIds::Attachments);
1543  sizeLength = EbmlElement::makeSizeDenotation(attachedFileElementsSize, buff);
1544  outputStream.write(buff, sizeLength);
1545  for(auto &maker : attachmentMaker) {
1546  maker.make(outputStream);
1547  }
1548  // no need to add notifications; this has been done when creating the make
1549  }
1550  }
1551 
1552  // write "Cues"-element
1553  if(newCuesPos == ElementPosition::BeforeData && segment.cuesElement) {
1554  try {
1555  segment.cuesUpdater.make(outputStream);
1556  addNotifications(segment.cuesUpdater);
1557  } catch(const Failure &) {
1558  addNotifications(segment.cuesUpdater);
1559  throw;
1560  }
1561  }
1562 
1563  // write padding / "Void"-element
1564  if(segment.newPadding) {
1565 
1566  // calculate length
1567  uint64 voidLength;
1568  if(segment.newPadding < 64) {
1569  sizeLength = 1;
1570  *buff = (voidLength = segment.newPadding - 2) | 0x80;
1571  } else {
1572  sizeLength = 8;
1573  BE::getBytes(static_cast<uint64>((voidLength = segment.newPadding - 9) | 0x100000000000000), buff);
1574  }
1575  // write header
1576  outputWriter.writeByte(EbmlIds::Void);
1577  outputStream.write(buff, sizeLength);
1578  // write zeroes
1579  for(; voidLength; --voidLength) {
1580  outputStream.put(0);
1581  }
1582  }
1583 
1584  // write media data / "Cluster"-elements
1585  level1Element = level0Element->childById(MatroskaIds::Cluster);
1586  if(rewriteRequired) {
1587 
1588  // update status, check whether the operation has been aborted
1589  if(isAborted()) {
1590  throw OperationAbortedException();
1591  }
1592  updateStatus("Writing clusters ...", static_cast<double>(static_cast<uint64>(outputStream.tellp()) - offset) / segment.totalDataSize);
1593  // write "Cluster"-element
1594  for(auto clusterSizesIterator = segment.clusterSizes.cbegin();
1595  level1Element; level1Element = level1Element->siblingById(MatroskaIds::Cluster), ++clusterSizesIterator) {
1596  // calculate position of cluster in segment
1597  clusterSize = currentPosition + (static_cast<uint64>(outputStream.tellp()) - offset);
1598  // write header; checking whether clusterSizesIterator is valid shouldn't be necessary
1599  outputWriter.writeUInt32BE(MatroskaIds::Cluster);
1600  sizeLength = EbmlElement::makeSizeDenotation(*clusterSizesIterator, buff);
1601  outputStream.write(buff, sizeLength);
1602  // write childs
1603  for(level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1604  switch(level2Element->id()) {
1605  case EbmlIds::Void:
1606  case EbmlIds::Crc32:
1607  break;
1608  case MatroskaIds::Position:
1609  EbmlElement::makeSimpleElement(outputStream, MatroskaIds::Position, clusterSize);
1610  break;
1611  default:
1612  level2Element->copyEntirely(outputStream);
1613  }
1614  }
1615  // update percentage, check whether the operation has been aborted
1616  if(isAborted()) {
1617  throw OperationAbortedException();
1618  } else {
1619  updatePercentage(static_cast<double>(static_cast<uint64>(outputStream.tellp()) - offset) / segment.totalDataSize);
1620  }
1621  }
1622  } else {
1623  // can't just skip existing "Cluster"-elements: "Position"-elements must be updated
1624  for(; level1Element; level1Element = level1Element->nextSibling()) {
1625  for(level2Element = level1Element->firstChild(); level2Element; level2Element = level2Element->nextSibling()) {
1626  switch(level2Element->id()) {
1627  case MatroskaIds::Position:
1628  // calculate new position
1629  sizeLength = EbmlElement::makeUInteger(level1Element->startOffset() - segmentData.front().newDataOffset, buff, level2Element->dataSize());
1630  // new position can only applied if it doesn't need more bytes than the previous position
1631  if(level2Element->dataSize() < sizeLength) {
1632  // can't update position -> void position elements ("Position"-elements seem a bit useless anyways)
1633  outputStream.seekp(level2Element->startOffset());
1634  outputStream.put(EbmlIds::Void);
1635  } else {
1636  // update position
1637  outputStream.seekp(level2Element->dataOffset());
1638  outputStream.write(buff, sizeLength);
1639  }
1640  break;
1641  default:
1642  ;
1643  }
1644  }
1645  }
1646  // skip existing "Cluster"-elements
1647  outputStream.seekp(segment.clusterEndOffset);
1648  }
1649 
1650  // write "Cues"-element
1651  if(newCuesPos == ElementPosition::AfterData && segment.cuesElement) {
1652  try {
1653  segment.cuesUpdater.make(outputStream);
1654  addNotifications(segment.cuesUpdater);
1655  } catch(const Failure &) {
1656  addNotifications(segment.cuesUpdater);
1657  throw;
1658  }
1659  }
1660 
1661  if(newTagPos == ElementPosition::AfterData && segmentIndex == lastSegmentIndex) {
1662  // write "Tags"-element
1663  if(tagsSize) {
1664  outputWriter.writeUInt32BE(MatroskaIds::Tags);
1665  sizeLength = EbmlElement::makeSizeDenotation(tagElementsSize, buff);
1666  outputStream.write(buff, sizeLength);
1667  for(auto &maker : tagMaker) {
1668  maker.make(outputStream);
1669  }
1670  // no need to add notifications; this has been done when creating the make
1671  }
1672  // write "Attachments"-element
1673  if(attachmentsSize) {
1674  outputWriter.writeUInt32BE(MatroskaIds::Attachments);
1675  sizeLength = EbmlElement::makeSizeDenotation(attachedFileElementsSize, buff);
1676  outputStream.write(buff, sizeLength);
1677  for(auto &maker : attachmentMaker) {
1678  maker.make(outputStream);
1679  }
1680  // no need to add notifications; this has been done when creating the make
1681  }
1682  }
1683 
1684  // increase the current segment index
1685  ++segmentIndex;
1686 
1687  // increase write offsets by the size of the segment which has just been written
1688  currentPosition += segment.totalSize;
1689 
1690  break;
1691 
1692  } default:
1693  // just copy any unknown top-level elements
1694  level0Element->copyEntirely(outputStream);
1695  currentPosition += level0Element->totalSize();
1696  }
1697  }
1698 
1699  // reparse what is written so far
1700  updateStatus("Reparsing output file ...");
1701  if(rewriteRequired) {
1702  // report new size
1703  fileInfo().reportSizeChanged(outputStream.tellp());
1704 
1705  // "save as path" is now the regular path
1706  if(!fileInfo().saveFilePath().empty()) {
1707  fileInfo().reportPathChanged(fileInfo().saveFilePath());
1708  fileInfo().setSaveFilePath(string());
1709  }
1710 
1711  // the outputStream needs to be reopened to be able to read again
1712  outputStream.close();
1713  outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1714  setStream(outputStream);
1715  } else {
1716  const auto newSize = static_cast<uint64>(outputStream.tellp());
1717  if(newSize < fileInfo().size()) {
1718  // file is smaller after the modification -> truncate
1719  // -> close stream before truncating
1720  outputStream.close();
1721  // -> truncate file
1722  if(truncate(fileInfo().path().c_str(), newSize) == 0) {
1723  fileInfo().reportSizeChanged(newSize);
1724  } else {
1725  addNotification(NotificationType::Critical, "Unable to truncate the file.", context);
1726  }
1727  // -> reopen the stream again
1728  outputStream.open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
1729  } else {
1730  // file is longer after the modification -> just report new size
1731  fileInfo().reportSizeChanged(newSize);
1732  }
1733  }
1734  reset();
1735  try {
1736  parseHeader();
1737  } catch(const Failure &) {
1738  addNotification(NotificationType::Critical, "Unable to reparse the header of the new file.", context);
1739  throw;
1740  }
1741 
1742  // update CRC-32 checksums
1743  if(!crc32Offsets.empty()) {
1744  updateStatus("Updating CRC-32 checksums ...");
1745  for(const auto &crc32Offset : crc32Offsets) {
1746  outputStream.seekg(get<0>(crc32Offset) + 6);
1747  outputStream.seekp(get<0>(crc32Offset) + 2);
1748  writer().writeUInt32LE(reader().readCrc32(get<1>(crc32Offset) - 6));
1749  }
1750  }
1751 
1752  updatePercentage(100.0);
1753 
1754  // flush output stream
1755  outputStream.flush();
1756 
1757  // handle errors (which might have been occured after renaming/creating backup file)
1758  } catch(...) {
1759  BackupHelper::handleFailureAfterFileModified(fileInfo(), backupPath, outputStream, backupStream, context);
1760  }
1761 }
1762 
1763 }
static constexpr uint32 maximumIdLengthSupported()
Returns the maximum id length supported by the class in byte.
IoUtilities::BinaryWriter & writer()
Returns the related BinaryWriter.
implementationType * childById(const identifierType &id)
Returns the first child with the specified id.
uint64 startOffset() const
Returns the start offset in the related stream.
EbmlElement * firstElement() const
Returns the first element of the file if available; otherwiese returns nullptr.
void invalidateStatus()
Invalidates the current status.
void make(std::ostream &stream)
Writes the previously parsed "Cues"-element with updates positions to the specified stream...
uint64 dataOffset() const
Returns the data offset of the element in the related stream.
bool isAborted() const
Returns an indication whether the current operation should be aborted.
constexpr uint64 appInfoElementDataSize
static byte calculateSizeDenotationLength(uint64 size)
Returns the length of the size denotation for the specified size in byte.
uint32 headerSize() const
Returns the header size of the element in byte.
implementationType * nextSibling()
Returns the next sibling of the element.
bool sameOffset(uint64 offset, const EbmlElement *element)
Returns an indication whether offset equals the start offset of element.
bool isIgnored() const
Returns whether the attachment is ignored/omitted when rewriting the container.
void validateIndex()
Validates the file index (cue entries).
void internalMakeFile()
Internally called to make the file.
uint64 readUInteger()
Reads the content of the element as unsigned integer.
void reset()
Discards all parsing results.
ChronoUtilities::TimeSpan m_duration
The EbmlElement class helps to parse EBML files such as Matroska files.
Definition: ebmlelement.h:50
The private SegmentData struct is used in MatroskaContainer::internalMakeFile() to store segment spec...
std::size_t chapterCount() const
Returns the number of chapters the container holds.
void internalParseAttachments()
Internally called to parse the attachments.
dataSizeType dataSize() const
Returns the data size of the element in byte.
static byte makeUInteger(uint64 value, char *buff)
Writes value to buff.
uint64 newDataOffset
data offset of the segment in the new file
constexpr const char appInfo[]
The GenericContainer class helps parsing header, track, tag and chapter information of a file...
ElementPosition determineTagPosition() const
Determines the position of the tags inside the file.
void discardBuffer()
Discards buffered data.
void setId(const uint64 &id)
Sets the ID of the attachment.
TAG_PARSER_EXPORT void createBackupFile(const std::string &originalPath, std::string &backupPath, IoUtilities::NativeFileStream &originalStream, IoUtilities::NativeFileStream &backupStream)
void parse()
Parses the header information of the element which is read from the related stream at the start offse...
EbmlElement * firstClusterElement
first "Cluster"-element (original file)
ElementPosition determineIndexPosition() const
Determines the position of the index.
TAG_PARSER_EXPORT void handleFailureAfterFileModified(MediaFileInfo &mediaFileInfo, const std::string &backupPath, IoUtilities::NativeFileStream &outputStream, IoUtilities::NativeFileStream &backupStream, const std::string &context="making file")
uint64 totalSize() const
Returns the total size of the element.
STL namespace.
The exception that is thrown when an operation has been stopped and thus not successfully completed b...
Definition: exceptions.h:43
uint64 totalSize() const
Returns how many bytes will be written when calling the make() method.
void addNotification(const Notification &notification)
This protected method is meant to be called by the derived class to add a notification.
void reportPathChanged(const std::string &newPath)
Call this function to report that the path changed.
MatroskaTagMaker prepareMaking()
Prepares making.
uint64 size() const
Returns size of the current file in bytes.
static void makeSimpleElement(std::ostream &stream, identifierType id, uint64 content)
Makes a simple EBML element.
void updatePercentage(double percentage)
This method is meant to be called by the derived class to report updated progress percentage only...
bool updateOffsets(uint64 originalOffset, uint64 newOffset)
Sets the offset of the entries with the specified originalOffset to newOffset.
The MatroskaCuePositionUpdater class helps to rewrite the "Cues"-element with shifted positions...
Definition: matroskacues.h:62
ElementPosition determineElementPosition(uint64 elementId) const
Determines the position of the element with the specified elementId.
uint64 totalDataSize
total size of the segment data (in the new file, excluding header)
bool isForcingRewrite() const
Returns whether forcing rewriting (when applying changes) is enabled.
void setStream(std::iostream &stream)
Sets the related stream.
const std::string & saveFilePath() const
Returns the "save file path" which has been set using setSaveFilePath().
The MatroskaChapter class provides an implementation of AbstractAttachment for Matroska files...
const std::vector< std::unique_ptr< MatroskaTag > > & tags() const
Returns the tags of the file.
void close()
A possibly opened std::fstream will be closed.
static byte calculateUIntegerLength(uint64 integer)
Returns the length of the specified unsigned integer in byte.
void internalParseTags()
Internally called to parse the tags.
IoUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
Definition: basicfileinfo.h:80
TAG_PARSER_EXPORT const char * title()
SegmentData()
Constructs a new segment data object.
void setSaveFilePath(const std::string &saveFilePath)
Sets the "save file path".
The MatroskaSeekInfo class helps parsing and making "SeekHead"-elements.
void internalParseHeader()
Internally called to parse the header.
static byte makeSizeDenotation(uint64 size, char *buff)
Makes the size denotation for the specified size and stores it to buff.
Contains utility classes helping to read and write streams.
uint64 startOffset() const
Returns the start offset in the related stream.
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:27
size_t preferredPadding() const
Returns the padding to be written before the data block when applying changes and the file needs to b...
std::string readString()
Reads the content of the element as string.
implementationType * siblingById(const identifierType &id, bool includeThis=false)
Returns the first sibling with the specified id.
byte sizeDenotationLength
header size (in the new file)
uint32 timeScale() const
Returns the time scale of the file if known; otherwise returns 0.
bool isHeaderParsed() const
Returns an indication whether the header has been parsed yet.
Implementation of Media::AbstractTrack for the Matroska container.
Definition: matroskatrack.h:11
bool updateRelativeOffsets(uint64 referenceOffset, uint64 originalRelativeOffset, uint64 newRelativeOffset)
Sets the relative offset of the entries with the specified originalRelativeOffset and the specified r...
std::string idToString() const
Converts the specified EBML ID to a printable string.
Definition: ebmlelement.h:90
void parseHeader()
Parses the header if not parsed yet.
MatroskaCuePositionUpdater cuesUpdater
used to make "Cues"-element
implementationType * firstChild()
Returns the first child of the element.
void reset()
Discards all parsing results.
const identifierType & id() const
Returns the element ID.
ElementPosition indexPosition() const
Returns the position (in the output file) where the index is written when applying changes...
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
bool push(unsigned int index, EbmlElement::identifierType id, uint64 offset)
Pushes the specified offset of an element with the specified id to the info.
void reportSizeChanged(uint64 newSize)
Call this function to report that the size changed.
vector< uint64 > clusterSizes
cluster sizes
float64 readFloat()
Reads the content of the element as float.
static constexpr uint32 maximumSizeLengthSupported()
Returns the maximum size length supported by the class in byte.
void invalidateNotifications()
Invalidates the object&#39;s notifications.
EbmlElement * cuesElement
"Cues"-element (original file)
The MediaFileInfo class allows to read and write tag information providing a container/tag format ind...
Definition: mediafileinfo.h:52
void makeBuffer()
Buffers the element (header and data).
std::vector< std::string > m_titles
void copyEntirely(std::ostream &targetStream)
Writes the entire element including all childs to the specified targetStream.
uint64 clusterEndOffset
end offset of last "Cluster"-element (original file)
IoUtilities::BinaryReader & reader()
Returns the related BinaryReader.
uint64 newPadding
padding (in the new file)
bool hasCrc32
whether CRC-32 checksum is present
void make(std::ostream &stream)
Writes a "SeekHead" element for the current instance to the specified stream.
void copyBuffer(std::ostream &targetStream)
Copies buffered data to targetStream.
void internalParseTracks()
Internally called to parse the tracks.
uint64 startOffset
start offset (in the new file)
bool excludesOffset(const vector< EbmlElement *> &elements, uint64 offset)
Returns whether none of the specified elements have the specified offset.
The exception that is thrown when the data to be parsed holds no parsable information.
Definition: exceptions.h:19
Implementation of Media::Tag for the Matroska container.
Definition: matroskatag.h:50
uint64 totalSize
total size of the segment data (in the new file, including header)
MatroskaAttachmentMaker prepareMaking()
Prepares making.
constexpr uint64 appInfoElementTotalSize
ElementPosition tagPosition() const
Returns the position (in the output file) where the tag information is written when applying changes...
MatroskaAttachment * createAttachment()
Creates and returns a new attachment.
void updateStatus(const std::string &status)
This method is meant to be called by the derived class to report updated status information.
uint64 id() const
Returns the ID of the attachment.
MatroskaSeekInfo seekInfo
used to make "SeekHead"-element
Contains all classes and functions of the TagInfo library.
Definition: exceptions.h:9
void internalParseChapters()
Internally called to parse the chapters.
MatroskaChapter * chapter(std::size_t index)
Returns the chapter with the specified index.
void addNotifications(const StatusProvider &from)
This protected method is meant to be called by the derived class to add all notifications from anothe...
uint64 infoDataSize
size of the "SegmentInfo"-element
Implementation of Media::AbstractAttachment for the Matroska container.
MatroskaAttachment * attachment(std::size_t index)
Returns the attachment with the specified index.
uint64 endOffset() const
Returns the offset of the first byte which doesn&#39;t belong to this element anymore.
uint64 actualSize() const
Returns the number of bytes which will be written when calling the make() method. ...
void parse(EbmlElement *cuesElement)
Parses the specified cuesElement.