Tag Parser  10.0.0
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 CppUtilities;
8 
9 namespace TagParser {
10 
38 std::uint64_t MatroskaCuePositionUpdater::totalSize() const
39 {
40  if (m_cuesElement) {
41  std::uint64_t 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  std::uint64_t cuesElementSize = 0, cuePointElementSize, cueTrackPositionsElementSize, cueReferenceElementSize, pos, relPos, statePos;
57  EbmlElement *cueRelativePositionElement, *cueClusterPositionElement;
58  for (EbmlElement *cuePointElement = cuesElement->firstChild(); cuePointElement; cuePointElement = cuePointElement->nextSibling()) {
59  // parse children 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 children 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 children 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 += 2u + EbmlElement::calculateUIntegerLength(pos);
98  m_offsets.emplace(cueTrackPositionsChild, pos);
99  m_cueElementByOriginalOffset.emplace(pos, cueTrackPositionsChild);
100  break;
102  statePos = cueTrackPositionsChild->readUInteger();
103  cueTrackPositionsElementSize += 2u + EbmlElement::calculateUIntegerLength(statePos);
104  m_offsets.emplace(cueTrackPositionsChild, statePos);
105  m_cueElementByOriginalOffset.emplace(statePos, cueTrackPositionsChild);
106  break;
108  cueReferenceElementSize = 0;
109  for (EbmlElement *cueReferenceChild = cueTrackPositionsChild->firstChild(); cueReferenceChild;
110  cueReferenceChild = cueReferenceChild->nextSibling()) {
111  // parse children of "CueReference"-element
112  cueReferenceChild->parse(diag);
113  switch (cueReferenceChild->id()) {
114  case EbmlIds::Void:
115  case EbmlIds::Crc32:
116  break;
119  cueReferenceChild->makeBuffer();
120  cueReferenceElementSize += cueReferenceChild->totalSize();
121  break;
124  statePos = cueReferenceChild->readUInteger();
125  cueReferenceElementSize += 2u + EbmlElement::calculateUIntegerLength(statePos);
126  m_offsets.emplace(cueReferenceChild, statePos);
127  m_cueElementByOriginalOffset.emplace(statePos, cueReferenceChild);
128  break;
129  default:
130  diag.emplace_back(DiagLevel::Warning,
131  "\"CueReference\"-element contains a element which is not known to the parser. It will be ignored.", context);
132  }
133  }
134  cueTrackPositionsElementSize
135  += 1 + EbmlElement::calculateSizeDenotationLength(cueReferenceElementSize) + cueReferenceElementSize;
136  m_sizes.emplace(cueTrackPositionsChild, cueReferenceElementSize);
137  break;
138  default:
139  diag.emplace_back(DiagLevel::Warning,
140  "\"CueTrackPositions\"-element contains a element which is not known to the parser. It will be ignored.", context);
141  }
142  }
143  if (!cueClusterPositionElement) {
144  diag.emplace_back(
145  DiagLevel::Critical, "\"CueTrackPositions\"-element does not contain mandatory \"CueClusterPosition\"-element.", context);
146  } else if (cueRelativePositionElement) {
147  cueTrackPositionsElementSize += 2u + EbmlElement::calculateUIntegerLength(relPos);
148  m_relativeOffsets.emplace(piecewise_construct, forward_as_tuple(cueRelativePositionElement), forward_as_tuple(pos, relPos));
149  m_cueRelativePositionElementByOriginalOffsets.emplace(
150  piecewise_construct, forward_as_tuple(pos, relPos), forward_as_tuple(cueRelativePositionElement));
151  }
152  cuePointElementSize
153  += 1 + EbmlElement::calculateSizeDenotationLength(cueTrackPositionsElementSize) + cueTrackPositionsElementSize;
154  m_sizes.emplace(cuePointChild, cueTrackPositionsElementSize);
155  break;
156  default:
157  diag.emplace_back(DiagLevel::Warning,
158  "\"CuePoint\"-element contains a element which is not a \"CueTime\"- or a \"CueTrackPositions\"-element. It will be ignored.",
159  context);
160  }
161  }
162  cuesElementSize += 1 + EbmlElement::calculateSizeDenotationLength(cuePointElementSize) + cuePointElementSize;
163  m_sizes.emplace(cuePointElement, cuePointElementSize);
164  break;
165  default:
166  diag.emplace_back(
167  DiagLevel::Warning, "\"Cues\"-element contains a element which is not a \"CuePoint\"-element. It will be ignored.", context);
168  }
169  }
170  m_sizes.emplace(m_cuesElement = cuesElement, cuesElementSize);
171 }
172 
177 bool MatroskaCuePositionUpdater::updateOffsets(std::uint64_t originalOffset, std::uint64_t newOffset)
178 {
179  auto updated = false;
180  const auto newOffsetLength = static_cast<int>(EbmlElement::calculateUIntegerLength(newOffset));
181  for (auto cueElementRange = m_cueElementByOriginalOffset.equal_range(originalOffset); cueElementRange.first != cueElementRange.second;
182  ++cueElementRange.first) {
183  auto *const cueElement = cueElementRange.first->second;
184  const auto offsetIterator = m_offsets.find(cueElement);
185  if (offsetIterator == m_offsets.end()) {
186  continue;
187  }
188  auto &offset = offsetIterator->second;
189  if (offset.currentValue() != newOffset) {
190  updated
191  = updateSize(cueElement->parent(), newOffsetLength - static_cast<int>(EbmlElement::calculateUIntegerLength(offset.currentValue())))
192  || updated;
193  offset.update(newOffset);
194  }
195  }
196  return updated;
197 }
198 
203 bool MatroskaCuePositionUpdater::updateRelativeOffsets(
204  std::uint64_t referenceOffset, std::uint64_t originalRelativeOffset, std::uint64_t newRelativeOffset)
205 {
206  auto updated = false;
207  const auto newRelativeOffsetLength = static_cast<int>(EbmlElement::calculateUIntegerLength(newRelativeOffset));
208  for (auto cueElementRange = m_cueRelativePositionElementByOriginalOffsets.equal_range(std::make_pair(referenceOffset, originalRelativeOffset));
209  cueElementRange.first != cueElementRange.second; ++cueElementRange.first) {
210  auto *const cueRelativePositionElement = cueElementRange.first->second;
211  const auto offsetIterator = m_relativeOffsets.find(cueRelativePositionElement);
212  if (offsetIterator == m_relativeOffsets.end()) {
213  continue;
214  }
215  auto &offset = offsetIterator->second;
216  if (offset.currentValue() != newRelativeOffset) {
217  updated = updateSize(cueRelativePositionElement->parent(),
218  newRelativeOffsetLength - static_cast<int>(EbmlElement::calculateUIntegerLength(offset.currentValue())))
219  || updated;
220  offset.update(newRelativeOffset);
221  }
222  }
223  return updated;
224 }
225 
230 bool MatroskaCuePositionUpdater::updateSize(EbmlElement *element, int shift)
231 {
232  if (!shift) {
233  return false; // shift is gone
234  }
235  if (!element) {
236  // there was no parent (shouldn't happen in a normal file structure since the Segment element should
237  // be parent of the Cues element)
238  return shift;
239  }
240  // get size info
241  const auto sizeIterator = m_sizes.find(element);
242  if (sizeIterator == m_sizes.end()) {
243  return shift; // the element is out of the scope of the cue position updater (likely the Segment element)
244  }
245  std::uint64_t &size = sizeIterator->second;
246  // calculate new size
247  const std::uint64_t newSize = shift > 0 ? size + static_cast<std::uint64_t>(shift) : size - static_cast<std::uint64_t>(-shift);
248  // shift parent
249  const bool updated = updateSize(element->parent(),
250  shift + static_cast<int>(EbmlElement::calculateSizeDenotationLength(newSize))
251  - static_cast<int>(EbmlElement::calculateSizeDenotationLength(size)));
252  // apply new size
253  size = newSize;
254  return updated;
255 }
256 
260 void MatroskaCuePositionUpdater::make(ostream &stream, Diagnostics &diag)
261 {
262  static const string context("making \"Cues\"-element");
263  if (!m_cuesElement) {
264  diag.emplace_back(DiagLevel::Warning, "No cues written; the cues of the source file could not be parsed correctly.", context);
265  return;
266  }
267  // temporary variables
268  char buff[8];
269  std::uint8_t len;
270  // write "Cues"-element
271  try {
272  BE::getBytes(static_cast<std::uint32_t>(MatroskaIds::Cues), buff);
273  stream.write(buff, 4);
274  len = EbmlElement::makeSizeDenotation(m_sizes[m_cuesElement], buff);
275  stream.write(buff, len);
276  // loop through original elements and write (a updated version) of them
277  for (EbmlElement *cuePointElement = m_cuesElement->firstChild(); cuePointElement; cuePointElement = cuePointElement->nextSibling()) {
278  cuePointElement->parse(diag);
279  switch (cuePointElement->id()) {
280  case EbmlIds::Void:
281  case EbmlIds::Crc32:
282  break;
284  // write "CuePoint"-element
285  stream.put(static_cast<char>(MatroskaIds::CuePoint));
286  len = EbmlElement::makeSizeDenotation(m_sizes[cuePointElement], buff);
287  stream.write(buff, len);
288  for (EbmlElement *cuePointChild = cuePointElement->firstChild(); cuePointChild; cuePointChild = cuePointChild->nextSibling()) {
289  cuePointChild->parse(diag);
290  switch (cuePointChild->id()) {
291  case EbmlIds::Void:
292  case EbmlIds::Crc32:
293  break;
295  // write "CueTime"-element
296  cuePointChild->copyBuffer(stream);
297  cuePointChild->discardBuffer();
298  break;
300  // write "CueTrackPositions"-element
301  stream.put(static_cast<char>(MatroskaIds::CueTrackPositions));
302  len = EbmlElement::makeSizeDenotation(m_sizes[cuePointChild], buff);
303  stream.write(buff, len);
304  for (EbmlElement *cueTrackPositionsChild = cuePointChild->firstChild(); cueTrackPositionsChild;
305  cueTrackPositionsChild = cueTrackPositionsChild->nextSibling()) {
306  cueTrackPositionsChild->parse(diag);
307  switch (cueTrackPositionsChild->id()) {
311  // write unchanged children of "CueTrackPositions"-element
312  cueTrackPositionsChild->copyBuffer(stream);
313  cueTrackPositionsChild->discardBuffer();
314  break;
316  if (const auto relativeOffset = m_relativeOffsets.find(cueTrackPositionsChild);
317  relativeOffset != m_relativeOffsets.end()) {
318  EbmlElement::makeSimpleElement(stream, cueTrackPositionsChild->id(), relativeOffset->second.currentValue());
319  }
320  // we were not able parse the relative offset because the absolute offset is missing
321  // continue anyways
322  break;
325  // write "CueClusterPosition"/"CueCodecState"-element
326  EbmlElement::makeSimpleElement(
327  stream, cueTrackPositionsChild->id(), m_offsets.at(cueTrackPositionsChild).currentValue());
328  break;
330  // write "CueReference"-element
331  stream.put(static_cast<char>(MatroskaIds::CueRefTime));
332  len = EbmlElement::makeSizeDenotation(m_sizes[cueTrackPositionsChild], buff);
333  stream.write(buff, len);
334  for (EbmlElement *cueReferenceChild = cueTrackPositionsChild->firstChild(); cueReferenceChild;
335  cueReferenceChild = cueReferenceChild->nextSibling()) {
336  cueReferenceChild->parse(diag);
337  switch (cueReferenceChild->id()) {
338  case EbmlIds::Void:
339  case EbmlIds::Crc32:
340  break;
343  // write unchanged children of "CueReference"-element
344  cueReferenceChild->copyBuffer(stream);
345  cueReferenceChild->discardBuffer();
346  cueReferenceChild->copyEntirely(stream, diag, nullptr);
347  break;
350  // write "CueRefCluster"/"CueRefCodecState"-element
351  EbmlElement::makeSimpleElement(
352  stream, cueReferenceChild->id(), m_offsets.at(cueReferenceChild).currentValue());
353  break;
354  default:
355  diag.emplace_back(DiagLevel::Warning,
356  "\"CueReference\"-element contains a element which is not known to the parser. It will be ignored.",
357  context);
358  }
359  }
360  break;
361  default:
362  diag.emplace_back(DiagLevel::Warning,
363  "\"CueTrackPositions\"-element contains a element which is not known to the parser. It will be ignored.",
364  context);
365  }
366  }
367  break;
368  default:
369  diag.emplace_back(DiagLevel::Warning,
370  "\"CuePoint\"-element contains a element which is not a \"CueTime\"- or a \"CueTrackPositions\"-element. It will be "
371  "ignored.",
372  context);
373  }
374  }
375  break;
376  default:
377  diag.emplace_back(
378  DiagLevel::Warning, "\"Cues\"-element contains a element which is not a \"CuePoint\"-element. It will be ignored.", context);
379  }
380  }
381  } catch (const out_of_range &) {
382  diag.emplace_back(
383  DiagLevel::Critical, "Unable to write the file index because the index of the original file could not be parsed correctly.", context);
384  throw InvalidDataException();
385  }
386 }
387 
388 } // namespace TagParser
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
The EbmlElement class helps to parse EBML files such as Matroska files.
Definition: ebmlelement.h:32
ImplementationType * nextSibling()
Returns the next sibling of the element.
ImplementationType * parent()
Returns the parent of the element.
ImplementationType * firstChild()
Returns the first child of the element.
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:10