Tag Parser  7.0.3
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(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(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(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.
void parse(Diagnostics &diag)
Parses the header information of the element which is read from the related stream at the start offse...
ImplementationType * parent()
Returns the parent of the element.