Tag Parser 11.4.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"
3
4#include <c++utilities/conversion/binaryconversion.h>
5
6using namespace std;
7using namespace CppUtilities;
8
9namespace TagParser {
10
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
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
177bool 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
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
230bool 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
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
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
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
static void makeSimpleElement(std::ostream &stream, IdentifierType id, std::uint64_t content)
Makes a simple EBML element.
static std::uint8_t calculateSizeDenotationLength(std::uint64_t size)
Returns the length of the size denotation for the specified size in byte.
static std::uint8_t makeSizeDenotation(std::uint64_t size, char *buff)
Makes the size denotation for the specified size and stores it to buff.
static std::uint8_t calculateUIntegerLength(std::uint64_t integer)
Returns the length of the specified unsigned integer in byte.
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
std::uint64_t totalSize() const
Returns how many bytes will be written when calling the make() method.
bool updateOffsets(std::uint64_t originalOffset, std::uint64_t newOffset)
Sets the offset of the entries with the specified originalOffset to newOffset.
bool updateRelativeOffsets(std::uint64_t referenceOffset, std::uint64_t originalRelativeOffset, std::uint64_t newRelativeOffset)
Sets the relative offset of the entries with the specified originalRelativeOffset and the specified r...
void clear()
Resets the object to its initial state.
Definition: matroskacues.h:121
void make(std::ostream &stream, Diagnostics &diag)
Writes the previously parsed "Cues"-element with updated positions to the specified stream.
void parse(EbmlElement *cuesElement, Diagnostics &diag)
Parses the specified cuesElement.
EbmlElement * cuesElement() const
Returns the "Cues"-element specified when calling the parse() method.
Definition: matroskacues.h:113
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10