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