Tag Parser  9.3.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 += 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 children 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(std::uint64_t originalOffset, std::uint64_t 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(
192  std::uint64_t referenceOffset, std::uint64_t originalRelativeOffset, std::uint64_t newRelativeOffset)
193 {
194  bool updated = false;
195  for (auto &offset : m_relativeOffsets) {
196  if (offset.second.referenceOffset() == referenceOffset && offset.second.initialValue() == originalRelativeOffset
197  && offset.second.currentValue() != newRelativeOffset) {
198  updated = updateSize(offset.first->parent(),
199  static_cast<int>(EbmlElement::calculateUIntegerLength(newRelativeOffset))
200  - static_cast<int>(EbmlElement::calculateUIntegerLength(offset.second.currentValue())))
201  || updated;
202  offset.second.update(newRelativeOffset);
203  }
204  }
205  return updated;
206 }
207 
212 bool MatroskaCuePositionUpdater::updateSize(EbmlElement *element, int shift)
213 {
214  if (!shift) {
215  // shift is gone
216  return false;
217  }
218  if (!element) {
219  // there was no parent (shouldn't happen in a normal file structure since the Segment element should
220  // be parent of the Cues element)
221  return shift;
222  }
223  try {
224  // get size info
225  std::uint64_t &size = m_sizes.at(element);
226  // calculate new size
227  const std::uint64_t newSize = shift > 0 ? size + static_cast<std::uint64_t>(shift) : size - static_cast<std::uint64_t>(-shift);
228  // shift parent
229  const bool updated = updateSize(element->parent(),
230  shift + static_cast<int>(EbmlElement::calculateSizeDenotationLength(newSize))
231  - static_cast<int>(EbmlElement::calculateSizeDenotationLength(size)));
232  // apply new size
233  size = newSize;
234  return updated;
235  } catch (const out_of_range &) {
236  // the element is out of the scope of the cue position updater (likely the Segment element)
237  return shift;
238  }
239 }
240 
244 void MatroskaCuePositionUpdater::make(ostream &stream, Diagnostics &diag)
245 {
246  static const string context("making \"Cues\"-element");
247  if (!m_cuesElement) {
248  diag.emplace_back(DiagLevel::Warning, "No cues written; the cues of the source file could not be parsed correctly.", context);
249  return;
250  }
251  // temporary variables
252  char buff[8];
253  std::uint8_t len;
254  // write "Cues"-element
255  try {
256  BE::getBytes(static_cast<std::uint32_t>(MatroskaIds::Cues), buff);
257  stream.write(buff, 4);
258  len = EbmlElement::makeSizeDenotation(m_sizes[m_cuesElement], buff);
259  stream.write(buff, len);
260  // loop through original elements and write (a updated version) of them
261  for (EbmlElement *cuePointElement = m_cuesElement->firstChild(); cuePointElement; cuePointElement = cuePointElement->nextSibling()) {
262  cuePointElement->parse(diag);
263  switch (cuePointElement->id()) {
264  case EbmlIds::Void:
265  case EbmlIds::Crc32:
266  break;
268  // write "CuePoint"-element
269  stream.put(static_cast<char>(MatroskaIds::CuePoint));
270  len = EbmlElement::makeSizeDenotation(m_sizes[cuePointElement], buff);
271  stream.write(buff, len);
272  for (EbmlElement *cuePointChild = cuePointElement->firstChild(); cuePointChild; cuePointChild = cuePointChild->nextSibling()) {
273  cuePointChild->parse(diag);
274  switch (cuePointChild->id()) {
275  case EbmlIds::Void:
276  case EbmlIds::Crc32:
277  break;
279  // write "CueTime"-element
280  cuePointChild->copyBuffer(stream);
281  cuePointChild->discardBuffer();
282  //cuePointChild->copyEntirely(stream);
283  break;
285  // write "CueTrackPositions"-element
286  stream.put(static_cast<char>(MatroskaIds::CueTrackPositions));
287  len = EbmlElement::makeSizeDenotation(m_sizes[cuePointChild], buff);
288  stream.write(buff, len);
289  for (EbmlElement *cueTrackPositionsChild = cuePointChild->firstChild(); cueTrackPositionsChild;
290  cueTrackPositionsChild = cueTrackPositionsChild->nextSibling()) {
291  cueTrackPositionsChild->parse(diag);
292  switch (cueTrackPositionsChild->id()) {
296  // write unchanged children of "CueTrackPositions"-element
297  cueTrackPositionsChild->copyBuffer(stream);
298  cueTrackPositionsChild->discardBuffer();
299  //cueTrackPositionsChild->copyEntirely(stream);
300  break;
302  try {
303  EbmlElement::makeSimpleElement(
304  stream, cueTrackPositionsChild->id(), m_relativeOffsets.at(cueTrackPositionsChild).currentValue());
305  } catch (const out_of_range &) {
306  // we were not able parse the relative offset because the absolute offset is missing
307  // continue anyways
308  }
309  break;
312  // write "CueClusterPosition"/"CueCodecState"-element
313  EbmlElement::makeSimpleElement(
314  stream, cueTrackPositionsChild->id(), m_offsets.at(cueTrackPositionsChild).currentValue());
315  break;
317  // write "CueReference"-element
318  stream.put(static_cast<char>(MatroskaIds::CueRefTime));
319  len = EbmlElement::makeSizeDenotation(m_sizes[cueTrackPositionsChild], buff);
320  stream.write(buff, len);
321  for (EbmlElement *cueReferenceChild = cueTrackPositionsChild->firstChild(); cueReferenceChild;
322  cueReferenceChild = cueReferenceChild->nextSibling()) {
323  cueReferenceChild->parse(diag);
324  switch (cueReferenceChild->id()) {
325  case EbmlIds::Void:
326  case EbmlIds::Crc32:
327  break;
330  // write unchanged children of "CueReference"-element
331  cueReferenceChild->copyBuffer(stream);
332  cueReferenceChild->discardBuffer();
333  cueReferenceChild->copyEntirely(stream, diag, nullptr);
334  break;
337  // write "CueRefCluster"/"CueRefCodecState"-element
338  EbmlElement::makeSimpleElement(
339  stream, cueReferenceChild->id(), m_offsets.at(cueReferenceChild).currentValue());
340  break;
341  default:
342  diag.emplace_back(DiagLevel::Warning,
343  "\"CueReference\"-element contains a element which is not known to the parser. It will be ignored.",
344  context);
345  }
346  }
347  break;
348  default:
349  diag.emplace_back(DiagLevel::Warning,
350  "\"CueTrackPositions\"-element contains a element which is not known to the parser. It will be ignored.",
351  context);
352  }
353  }
354  break;
355  default:
356  diag.emplace_back(DiagLevel::Warning,
357  "\"CuePoint\"-element contains a element which is not a \"CueTime\"- or a \"CueTrackPositions\"-element. It will be "
358  "ignored.",
359  context);
360  }
361  }
362  break;
363  default:
364  diag.emplace_back(
365  DiagLevel::Warning, "\"Cues\"-element contains a element which is not a \"CuePoint\"-element. It will be ignored.", context);
366  }
367  }
368  } catch (const out_of_range &) {
369  diag.emplace_back(
370  DiagLevel::Critical, "Unable to write the file index because the index of the original file could not be parsed correctly.", context);
371  throw InvalidDataException();
372  }
373 }
374 
375 } // namespace TagParser
TagParser::MatroskaIds::CuePoint
@ CuePoint
Definition: matroskaid.h:241
TagParser::MatroskaIds::CueRelativePosition
@ CueRelativePosition
Definition: matroskaid.h:254
TagParser::MatroskaIds::Cues
@ Cues
Definition: matroskaid.h:19
TagParser::MatroskaIds::CueTrack
@ CueTrack
Definition: matroskaid.h:252
TagParser::MatroskaIds::CueRefCodecState
@ CueRefCodecState
Definition: matroskaid.h:264
TagParser::MatroskaIds::CueReference
@ CueReference
Definition: matroskaid.h:258
TagParser::MatroskaIds::CueTrackPositions
@ CueTrackPositions
Definition: matroskaid.h:246
TagParser::Diagnostics
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
TagParser::MatroskaIds::CueRefCluster
@ CueRefCluster
Definition: matroskaid.h:264
TagParser
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
TagParser::MatroskaIds::CueClusterPosition
@ CueClusterPosition
Definition: matroskaid.h:253
matroskacontainer.h
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::MatroskaIds::CueBlockNumber
@ CueBlockNumber
Definition: matroskaid.h:256
TagParser::MatroskaIds::CueDuration
@ CueDuration
Definition: matroskaid.h:255
TagParser::MatroskaIds::CueRefTime
@ CueRefTime
Definition: matroskaid.h:264
TagParser::GenericFileElement::firstChild
ImplementationType * firstChild()
Returns the first child of the element.
Definition: genericfileelement.h:460
TagParser::EbmlElement
The EbmlElement class helps to parse EBML files such as Matroska files.
Definition: ebmlelement.h:31
TagParser::MatroskaIds::CueTime
@ CueTime
Definition: matroskaid.h:246
CppUtilities
Definition: abstractcontainer.h:15
TagParser::EbmlIds::Void
@ Void
Definition: ebmlid.h:28
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::EbmlIds::Crc32
@ Crc32
Definition: ebmlid.h:28
matroskacues.h
TagParser::MatroskaIds::CueRefNumber
@ CueRefNumber
Definition: matroskaid.h:264
TagParser::GenericFileElement::parent
ImplementationType * parent()
Returns the parent of the element.
Definition: genericfileelement.h:385
TagParser::MatroskaIds::CueCodecState
@ CueCodecState
Definition: matroskaid.h:257