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