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