Tag Parser  6.5.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 Media {
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)
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();
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();
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; cueTrackPositionsChild = cueTrackPositionsChild->nextSibling()) {
82  // parse childs of "CueTrackPositions"-element
83  cueTrackPositionsChild->parse();
84  switch(cueTrackPositionsChild->id()) {
88  cueTrackPositionsChild->makeBuffer();
89  cueTrackPositionsElementSize += cueTrackPositionsChild->totalSize();
90  break;
92  relPos = (cueRelativePositionElement = cueTrackPositionsChild)->readUInteger();
93  break;
95  pos = (cueClusterPositionElement = cueTrackPositionsChild)->readUInteger();
96  cueTrackPositionsElementSize += 2 + EbmlElement::calculateUIntegerLength(pos);
97  m_offsets.emplace(cueTrackPositionsChild, pos);
98  break;
100  statePos = cueTrackPositionsChild->readUInteger();
101  cueTrackPositionsElementSize += 2 + EbmlElement::calculateUIntegerLength(statePos);
102  m_offsets.emplace(cueTrackPositionsChild, statePos);
103  break;
105  cueReferenceElementSize = 0;
106  for(EbmlElement *cueReferenceChild = cueTrackPositionsChild->firstChild(); cueReferenceChild; cueReferenceChild = cueReferenceChild->nextSibling()) {
107  // parse childs of "CueReference"-element
108  cueReferenceChild->parse();
109  switch(cueReferenceChild->id()) {
110  case EbmlIds::Void:
111  case EbmlIds::Crc32:
112  break;
115  cueReferenceChild->makeBuffer();
116  cueReferenceElementSize += cueReferenceChild->totalSize();
117  break;
120  statePos = cueReferenceChild->readUInteger();
121  cueReferenceElementSize += 2 + EbmlElement::calculateUIntegerLength(statePos);
122  m_offsets.emplace(cueReferenceChild, statePos);
123  break;
124  default:
125  addNotification(NotificationType::Warning, "\"CueReference\"-element contains a element which is not known to the parser. It will be ignored.", context);
126  }
127  }
128  cueTrackPositionsElementSize += 1 + EbmlElement::calculateSizeDenotationLength(cueReferenceElementSize) + cueReferenceElementSize;
129  m_sizes.emplace(cueTrackPositionsChild, cueReferenceElementSize);
130  break;
131  default:
132  addNotification(NotificationType::Warning, "\"CueTrackPositions\"-element contains a element which is not known to the parser. It will be ignored.", context);
133  }
134  }
135  if(!cueClusterPositionElement) {
136  addNotification(NotificationType::Critical, "\"CueTrackPositions\"-element does not contain mandatory \"CueClusterPosition\"-element.", context);
137  } else if(cueRelativePositionElement) {
138  cueTrackPositionsElementSize += 2 + EbmlElement::calculateUIntegerLength(relPos);
139  m_relativeOffsets.emplace(piecewise_construct, forward_as_tuple(cueRelativePositionElement), forward_as_tuple(pos, relPos));
140  }
141  cuePointElementSize += 1 + EbmlElement::calculateSizeDenotationLength(cueTrackPositionsElementSize) + cueTrackPositionsElementSize;
142  m_sizes.emplace(cuePointChild, cueTrackPositionsElementSize);
143  break;
144  default:
145  addNotification(NotificationType::Warning, "\"CuePoint\"-element contains a element which is not a \"CueTime\"- or a \"CueTrackPositions\"-element. It will be ignored.", context);
146  }
147  }
148  cuesElementSize += 1 + EbmlElement::calculateSizeDenotationLength(cuePointElementSize) + cuePointElementSize;
149  m_sizes.emplace(cuePointElement, cuePointElementSize);
150  break;
151  default:
152  addNotification(NotificationType::Warning, "\"Cues\"-element contains a element which is not a \"CuePoint\"-element. It will be ignored.", context);
153  }
154  }
155  m_sizes.emplace(m_cuesElement = cuesElement, cuesElementSize);
156 }
157 
162 bool MatroskaCuePositionUpdater::updateOffsets(uint64 originalOffset, uint64 newOffset)
163 {
164  bool updated = false;
165  for(auto &offset : m_offsets) {
166  if(offset.second.initialValue() == originalOffset && offset.second.currentValue() != newOffset) {
167  updated = updateSize(offset.first->parent(), static_cast<int>(EbmlElement::calculateUIntegerLength(newOffset)) - static_cast<int>(EbmlElement::calculateUIntegerLength(offset.second.currentValue()))) || updated;
168  offset.second.update(newOffset);
169  }
170  }
171  return updated;
172 }
173 
178 bool MatroskaCuePositionUpdater::updateRelativeOffsets(uint64 referenceOffset, uint64 originalRelativeOffset, uint64 newRelativeOffset)
179 {
180  bool updated = false;
181  for(auto &offset : m_relativeOffsets) {
182  if(offset.second.referenceOffset() == referenceOffset && offset.second.initialValue() == originalRelativeOffset && offset.second.currentValue() != newRelativeOffset) {
183  updated = updateSize(offset.first->parent(), static_cast<int>(EbmlElement::calculateUIntegerLength(newRelativeOffset)) - static_cast<int>(EbmlElement::calculateUIntegerLength(offset.second.currentValue()))) || updated;
184  offset.second.update(newRelativeOffset);
185  }
186  }
187  return updated;
188 }
189 
194 bool MatroskaCuePositionUpdater::updateSize(EbmlElement *element, int shift)
195 {
196  if(!shift) {
197  // shift is gone
198  return false;
199  }
200  if(!element) {
201  // there was no parent (shouldn't happen in a normal file structure since the Segment element should
202  // be parent of the Cues element)
203  return shift;
204  }
205  try {
206  // get size info
207  uint64 &size = m_sizes.at(element);
208  // calculate new size
209  const uint64 newSize = shift > 0 ? size + static_cast<uint64>(shift) : size - static_cast<uint64>(-shift);
210  // shift parent
211  const bool updated = updateSize(element->parent(), shift + static_cast<int>(EbmlElement::calculateSizeDenotationLength(newSize)) - static_cast<int>(EbmlElement::calculateSizeDenotationLength(size)));
212  // apply new size
213  size = newSize;
214  return updated;
215  } catch(const out_of_range &) {
216  // the element is out of the scope of the cue position updater (likely the Segment element)
217  return shift;
218  }
219 }
220 
224 void MatroskaCuePositionUpdater::make(ostream &stream)
225 {
226  static const string context("making \"Cues\"-element");
227  if(!m_cuesElement) {
228  addNotification(NotificationType::Warning, "No cues written; the cues of the source file could not be parsed correctly.", context);
229  return;
230  }
231  // temporary variables
232  char buff[8];
233  byte len;
234  // write "Cues"-element
235  try {
236  BE::getBytes(static_cast<uint32>(MatroskaIds::Cues), buff);
237  stream.write(buff, 4);
238  len = EbmlElement::makeSizeDenotation(m_sizes[m_cuesElement], buff);
239  stream.write(buff, len);
240  // loop through original elements and write (a updated version) of them
241  for(EbmlElement *cuePointElement = m_cuesElement->firstChild(); cuePointElement; cuePointElement = cuePointElement->nextSibling()) {
242  cuePointElement->parse();
243  switch(cuePointElement->id()) {
244  case EbmlIds::Void:
245  case EbmlIds::Crc32:
246  break;
248  // write "CuePoint"-element
249  stream.put(MatroskaIds::CuePoint);
250  len = EbmlElement::makeSizeDenotation(m_sizes[cuePointElement], buff);
251  stream.write(buff, len);
252  for(EbmlElement *cuePointChild = cuePointElement->firstChild(); cuePointChild; cuePointChild = cuePointChild->nextSibling()) {
253  cuePointChild->parse();
254  switch(cuePointChild->id()) {
255  case EbmlIds::Void:
256  case EbmlIds::Crc32:
257  break;
259  // write "CueTime"-element
260  cuePointChild->copyBuffer(stream);
261  cuePointChild->discardBuffer();
262  //cuePointChild->copyEntirely(stream);
263  break;
265  // write "CueTrackPositions"-element
266  stream.put(MatroskaIds::CueTrackPositions);
267  len = EbmlElement::makeSizeDenotation(m_sizes[cuePointChild], buff);
268  stream.write(buff, len);
269  for(EbmlElement *cueTrackPositionsChild = cuePointChild->firstChild(); cueTrackPositionsChild; cueTrackPositionsChild = cueTrackPositionsChild->nextSibling()) {
270  cueTrackPositionsChild->parse();
271  switch(cueTrackPositionsChild->id()) {
275  // write unchanged childs of "CueTrackPositions"-element
276  cueTrackPositionsChild->copyBuffer(stream);
277  cueTrackPositionsChild->discardBuffer();
278  //cueTrackPositionsChild->copyEntirely(stream);
279  break;
281  try {
282  EbmlElement::makeSimpleElement(stream, cueTrackPositionsChild->id(), m_relativeOffsets.at(cueTrackPositionsChild).currentValue());
283  } catch(const out_of_range &) {
284  // we were not able parse the relative offset because the absolute offset is missing
285  // continue anyways
286  }
287  break;
290  // write "CueClusterPosition"/"CueCodecState"-element
291  EbmlElement::makeSimpleElement(stream, cueTrackPositionsChild->id(), m_offsets.at(cueTrackPositionsChild).currentValue());
292  break;
294  // write "CueReference"-element
295  stream.put(MatroskaIds::CueRefTime);
296  len = EbmlElement::makeSizeDenotation(m_sizes[cueTrackPositionsChild], buff);
297  stream.write(buff, len);
298  for(EbmlElement *cueReferenceChild = cueTrackPositionsChild->firstChild(); cueReferenceChild; cueReferenceChild = cueReferenceChild->nextSibling()) {
299  cueReferenceChild->parse();
300  switch(cueReferenceChild->id()) {
301  case EbmlIds::Void:
302  case EbmlIds::Crc32:
303  break;
306  // write unchanged childs of "CueReference"-element
307  cueReferenceChild->copyBuffer(stream);
308  cueReferenceChild->discardBuffer();
309  cueReferenceChild->copyEntirely(stream);
310  break;
313  // write "CueRefCluster"/"CueRefCodecState"-element
314  EbmlElement::makeSimpleElement(stream, cueReferenceChild->id(), m_offsets.at(cueReferenceChild).currentValue());
315  break;
316  default:
317  addNotification(NotificationType::Warning, "\"CueReference\"-element contains a element which is not known to the parser. It will be ignored.", context);
318  }
319  }
320  break;
321  default:
322  addNotification(NotificationType::Warning, "\"CueTrackPositions\"-element contains a element which is not known to the parser. It will be ignored.", context);
323  }
324  }
325  break;
326  default:
327  addNotification(NotificationType::Warning, "\"CuePoint\"-element contains a element which is not a \"CueTime\"- or a \"CueTrackPositions\"-element. It will be ignored.", context);
328  }
329  }
330  break;
331  default:
332  addNotification(NotificationType::Warning, "\"Cues\"-element contains a element which is not a \"CuePoint\"-element. It will be ignored.", context);
333  }
334  }
335  } catch(const out_of_range &) {
336  addNotification(NotificationType::Critical, "Unable to write the file index because the index of the original file could not be parsed correctly.", context);
337  throw InvalidDataException();
338  }
339 }
340 
341 } // namespace Media
342 
The EbmlElement class helps to parse EBML files such as Matroska files.
Definition: ebmlelement.h:50
void parse()
Parses the header information of the element which is read from the related stream at the start offse...
STL namespace.
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:27
implementationType * firstChild()
Returns the first child of the element.
implementationType * parent()
Returns the parent of the element.
Contains all classes and functions of the TagInfo library.
Definition: exceptions.h:9