Tag Parser  8.0.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
matroskacues.cpp
Go to the documentation of this file.
1 #include "./matroskacues.h"
2 #include "./matroskacontainer.h"
3 
4 #include <c++utilities/conversion/binaryconversion.h>
5 
6 using namespace std;
7 using namespace ConversionUtilities;
8 
9 namespace TagParser {
10 
38 uint64 MatroskaCuePositionUpdater::totalSize() const
39 {
40  if (m_cuesElement) {
41  uint64 size = m_sizes.at(m_cuesElement);
42  return 4 + EbmlElement::calculateSizeDenotationLength(size) + size;
43  } else {
44  return 0;
45  }
46 }
47 
52 void MatroskaCuePositionUpdater::parse(EbmlElement *cuesElement, Diagnostics &diag)
53 {
54  static const string context("parsing \"Cues\"-element");
55  clear();
56  uint64 cuesElementSize = 0, cuePointElementSize, cueTrackPositionsElementSize, cueReferenceElementSize, pos, relPos, statePos;
57  EbmlElement *cueRelativePositionElement, *cueClusterPositionElement;
58  for (EbmlElement *cuePointElement = cuesElement->firstChild(); cuePointElement; cuePointElement = cuePointElement->nextSibling()) {
59  // parse childs of "Cues"-element which must be "CuePoint"-elements
60  cuePointElement->parse(diag);
61  switch (cuePointElement->id()) {
62  case EbmlIds::Void:
63  case EbmlIds::Crc32:
64  break;
66  cuePointElementSize = 0;
67  for (EbmlElement *cuePointChild = cuePointElement->firstChild(); cuePointChild; cuePointChild = cuePointChild->nextSibling()) {
68  // parse childs of "CuePoint"-element
69  cuePointChild->parse(diag);
70  switch (cuePointChild->id()) {
71  case EbmlIds::Void:
72  case EbmlIds::Crc32:
73  break;
75  cuePointChild->makeBuffer();
76  cuePointElementSize += cuePointChild->totalSize();
77  break;
79  cueTrackPositionsElementSize = 0;
80  cueRelativePositionElement = cueClusterPositionElement = nullptr;
81  for (EbmlElement *cueTrackPositionsChild = cuePointChild->firstChild(); cueTrackPositionsChild;
82  cueTrackPositionsChild = cueTrackPositionsChild->nextSibling()) {
83  // parse childs of "CueTrackPositions"-element
84  cueTrackPositionsChild->parse(diag);
85  switch (cueTrackPositionsChild->id()) {
89  cueTrackPositionsChild->makeBuffer();
90  cueTrackPositionsElementSize += cueTrackPositionsChild->totalSize();
91  break;
93  relPos = (cueRelativePositionElement = cueTrackPositionsChild)->readUInteger();
94  break;
96  pos = (cueClusterPositionElement = cueTrackPositionsChild)->readUInteger();
97  cueTrackPositionsElementSize += 2 + EbmlElement::calculateUIntegerLength(pos);
98  m_offsets.emplace(cueTrackPositionsChild, pos);
99  break;
101  statePos = cueTrackPositionsChild->readUInteger();
102  cueTrackPositionsElementSize += 2 + EbmlElement::calculateUIntegerLength(statePos);
103  m_offsets.emplace(cueTrackPositionsChild, statePos);
104  break;
106  cueReferenceElementSize = 0;
107  for (EbmlElement *cueReferenceChild = cueTrackPositionsChild->firstChild(); cueReferenceChild;
108  cueReferenceChild = cueReferenceChild->nextSibling()) {
109  // parse childs of "CueReference"-element
110  cueReferenceChild->parse(diag);
111  switch (cueReferenceChild->id()) {
112  case EbmlIds::Void:
113  case EbmlIds::Crc32:
114  break;
117  cueReferenceChild->makeBuffer();
118  cueReferenceElementSize += cueReferenceChild->totalSize();
119  break;
122  statePos = cueReferenceChild->readUInteger();
123  cueReferenceElementSize += 2 + EbmlElement::calculateUIntegerLength(statePos);
124  m_offsets.emplace(cueReferenceChild, statePos);
125  break;
126  default:
127  diag.emplace_back(DiagLevel::Warning,
128  "\"CueReference\"-element contains a element which is not known to the parser. It will be ignored.", context);
129  }
130  }
131  cueTrackPositionsElementSize
132  += 1 + EbmlElement::calculateSizeDenotationLength(cueReferenceElementSize) + cueReferenceElementSize;
133  m_sizes.emplace(cueTrackPositionsChild, cueReferenceElementSize);
134  break;
135  default:
136  diag.emplace_back(DiagLevel::Warning,
137  "\"CueTrackPositions\"-element contains a element which is not known to the parser. It will be ignored.", context);
138  }
139  }
140  if (!cueClusterPositionElement) {
141  diag.emplace_back(
142  DiagLevel::Critical, "\"CueTrackPositions\"-element does not contain mandatory \"CueClusterPosition\"-element.", context);
143  } else if (cueRelativePositionElement) {
144  cueTrackPositionsElementSize += 2 + EbmlElement::calculateUIntegerLength(relPos);
145  m_relativeOffsets.emplace(piecewise_construct, forward_as_tuple(cueRelativePositionElement), forward_as_tuple(pos, relPos));
146  }
147  cuePointElementSize
148  += 1 + EbmlElement::calculateSizeDenotationLength(cueTrackPositionsElementSize) + cueTrackPositionsElementSize;
149  m_sizes.emplace(cuePointChild, cueTrackPositionsElementSize);
150  break;
151  default:
152  diag.emplace_back(DiagLevel::Warning,
153  "\"CuePoint\"-element contains a element which is not a \"CueTime\"- or a \"CueTrackPositions\"-element. It will be ignored.",
154  context);
155  }
156  }
157  cuesElementSize += 1 + EbmlElement::calculateSizeDenotationLength(cuePointElementSize) + cuePointElementSize;
158  m_sizes.emplace(cuePointElement, cuePointElementSize);
159  break;
160  default:
161  diag.emplace_back(
162  DiagLevel::Warning, "\"Cues\"-element contains a element which is not a \"CuePoint\"-element. It will be ignored.", context);
163  }
164  }
165  m_sizes.emplace(m_cuesElement = cuesElement, cuesElementSize);
166 }
167 
172 bool MatroskaCuePositionUpdater::updateOffsets(uint64 originalOffset, uint64 newOffset)
173 {
174  bool updated = false;
175  for (auto &offset : m_offsets) {
176  if (offset.second.initialValue() == originalOffset && offset.second.currentValue() != newOffset) {
177  updated = updateSize(offset.first->parent(),
178  static_cast<int>(EbmlElement::calculateUIntegerLength(newOffset))
179  - static_cast<int>(EbmlElement::calculateUIntegerLength(offset.second.currentValue())))
180  || updated;
181  offset.second.update(newOffset);
182  }
183  }
184  return updated;
185 }
186 
191 bool MatroskaCuePositionUpdater::updateRelativeOffsets(uint64 referenceOffset, uint64 originalRelativeOffset, uint64 newRelativeOffset)
192 {
193  bool updated = false;
194  for (auto &offset : m_relativeOffsets) {
195  if (offset.second.referenceOffset() == referenceOffset && offset.second.initialValue() == originalRelativeOffset
196  && offset.second.currentValue() != newRelativeOffset) {
197  updated = updateSize(offset.first->parent(),
198  static_cast<int>(EbmlElement::calculateUIntegerLength(newRelativeOffset))
199  - static_cast<int>(EbmlElement::calculateUIntegerLength(offset.second.currentValue())))
200  || updated;
201  offset.second.update(newRelativeOffset);
202  }
203  }
204  return updated;
205 }
206 
211 bool MatroskaCuePositionUpdater::updateSize(EbmlElement *element, int shift)
212 {
213  if (!shift) {
214  // shift is gone
215  return false;
216  }
217  if (!element) {
218  // there was no parent (shouldn't happen in a normal file structure since the Segment element should
219  // be parent of the Cues element)
220  return shift;
221  }
222  try {
223  // get size info
224  uint64 &size = m_sizes.at(element);
225  // calculate new size
226  const uint64 newSize = shift > 0 ? size + static_cast<uint64>(shift) : size - static_cast<uint64>(-shift);
227  // shift parent
228  const bool updated = updateSize(element->parent(),
229  shift + static_cast<int>(EbmlElement::calculateSizeDenotationLength(newSize))
230  - static_cast<int>(EbmlElement::calculateSizeDenotationLength(size)));
231  // apply new size
232  size = newSize;
233  return updated;
234  } catch (const out_of_range &) {
235  // the element is out of the scope of the cue position updater (likely the Segment element)
236  return shift;
237  }
238 }
239 
243 void MatroskaCuePositionUpdater::make(ostream &stream, Diagnostics &diag)
244 {
245  static const string context("making \"Cues\"-element");
246  if (!m_cuesElement) {
247  diag.emplace_back(DiagLevel::Warning, "No cues written; the cues of the source file could not be parsed correctly.", context);
248  return;
249  }
250  // temporary variables
251  char buff[8];
252  byte len;
253  // write "Cues"-element
254  try {
255  BE::getBytes(static_cast<uint32>(MatroskaIds::Cues), buff);
256  stream.write(buff, 4);
257  len = EbmlElement::makeSizeDenotation(m_sizes[m_cuesElement], buff);
258  stream.write(buff, len);
259  // loop through original elements and write (a updated version) of them
260  for (EbmlElement *cuePointElement = m_cuesElement->firstChild(); cuePointElement; cuePointElement = cuePointElement->nextSibling()) {
261  cuePointElement->parse(diag);
262  switch (cuePointElement->id()) {
263  case EbmlIds::Void:
264  case EbmlIds::Crc32:
265  break;
267  // write "CuePoint"-element
268  stream.put(static_cast<char>(MatroskaIds::CuePoint));
269  len = EbmlElement::makeSizeDenotation(m_sizes[cuePointElement], buff);
270  stream.write(buff, len);
271  for (EbmlElement *cuePointChild = cuePointElement->firstChild(); cuePointChild; cuePointChild = cuePointChild->nextSibling()) {
272  cuePointChild->parse(diag);
273  switch (cuePointChild->id()) {
274  case EbmlIds::Void:
275  case EbmlIds::Crc32:
276  break;
278  // write "CueTime"-element
279  cuePointChild->copyBuffer(stream);
280  cuePointChild->discardBuffer();
281  //cuePointChild->copyEntirely(stream);
282  break;
284  // write "CueTrackPositions"-element
285  stream.put(static_cast<char>(MatroskaIds::CueTrackPositions));
286  len = EbmlElement::makeSizeDenotation(m_sizes[cuePointChild], buff);
287  stream.write(buff, len);
288  for (EbmlElement *cueTrackPositionsChild = cuePointChild->firstChild(); cueTrackPositionsChild;
289  cueTrackPositionsChild = cueTrackPositionsChild->nextSibling()) {
290  cueTrackPositionsChild->parse(diag);
291  switch (cueTrackPositionsChild->id()) {
295  // write unchanged childs of "CueTrackPositions"-element
296  cueTrackPositionsChild->copyBuffer(stream);
297  cueTrackPositionsChild->discardBuffer();
298  //cueTrackPositionsChild->copyEntirely(stream);
299  break;
301  try {
302  EbmlElement::makeSimpleElement(
303  stream, cueTrackPositionsChild->id(), m_relativeOffsets.at(cueTrackPositionsChild).currentValue());
304  } catch (const out_of_range &) {
305  // we were not able parse the relative offset because the absolute offset is missing
306  // continue anyways
307  }
308  break;
311  // write "CueClusterPosition"/"CueCodecState"-element
312  EbmlElement::makeSimpleElement(
313  stream, cueTrackPositionsChild->id(), m_offsets.at(cueTrackPositionsChild).currentValue());
314  break;
316  // write "CueReference"-element
317  stream.put(static_cast<char>(MatroskaIds::CueRefTime));
318  len = EbmlElement::makeSizeDenotation(m_sizes[cueTrackPositionsChild], buff);
319  stream.write(buff, len);
320  for (EbmlElement *cueReferenceChild = cueTrackPositionsChild->firstChild(); cueReferenceChild;
321  cueReferenceChild = cueReferenceChild->nextSibling()) {
322  cueReferenceChild->parse(diag);
323  switch (cueReferenceChild->id()) {
324  case EbmlIds::Void:
325  case EbmlIds::Crc32:
326  break;
329  // write unchanged childs of "CueReference"-element
330  cueReferenceChild->copyBuffer(stream);
331  cueReferenceChild->discardBuffer();
332  cueReferenceChild->copyEntirely(stream, diag, nullptr);
333  break;
336  // write "CueRefCluster"/"CueRefCodecState"-element
337  EbmlElement::makeSimpleElement(
338  stream, cueReferenceChild->id(), m_offsets.at(cueReferenceChild).currentValue());
339  break;
340  default:
341  diag.emplace_back(DiagLevel::Warning,
342  "\"CueReference\"-element contains a element which is not known to the parser. It will be ignored.",
343  context);
344  }
345  }
346  break;
347  default:
348  diag.emplace_back(DiagLevel::Warning,
349  "\"CueTrackPositions\"-element contains a element which is not known to the parser. It will be ignored.",
350  context);
351  }
352  }
353  break;
354  default:
355  diag.emplace_back(DiagLevel::Warning,
356  "\"CuePoint\"-element contains a element which is not a \"CueTime\"- or a \"CueTrackPositions\"-element. It will be "
357  "ignored.",
358  context);
359  }
360  }
361  break;
362  default:
363  diag.emplace_back(
364  DiagLevel::Warning, "\"Cues\"-element contains a element which is not a \"CuePoint\"-element. It will be ignored.", context);
365  }
366  }
367  } catch (const out_of_range &) {
368  diag.emplace_back(
369  DiagLevel::Critical, "Unable to write the file index because the index of the original file could not be parsed correctly.", context);
370  throw InvalidDataException();
371  }
372 }
373 
374 } // namespace TagParser
ImplementationType * firstChild()
Returns the first child of the element.
STL namespace.
The EbmlElement class helps to parse EBML files such as Matroska files.
Definition: ebmlelement.h:31
void parse(Diagnostics &diag)
Parses the header information of the element which is read from the related stream at the start offse...
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:25
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:9
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
ImplementationType * parent()
Returns the parent of the element.