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