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