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