Tag Parser 12.1.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
Loading...
Searching...
No Matches
oggcontainer.cpp
Go to the documentation of this file.
1#include "./oggcontainer.h"
2
3#include "../flac/flacmetadata.h"
4
5#include "../backuphelper.h"
6#include "../mediafileinfo.h"
7#include "../progressfeedback.h"
8#include "../tagtarget.h"
9
10#include <c++utilities/conversion/stringbuilder.h>
11#include <c++utilities/io/copy.h>
12
13#include <memory>
14
15using namespace std;
16using namespace CppUtilities;
17
18namespace TagParser {
19
25std::string_view OggVorbisComment::typeName() const
26{
27 switch (m_oggParams.streamFormat) {
29 return "Vorbis comment (in FLAC stream)";
31 return "Vorbis comment (in Opus stream)";
33 return "Vorbis comment (in Theora stream)";
34 default:
35 return "Vorbis comment";
36 }
37}
38
47OggContainer::OggContainer(MediaFileInfo &fileInfo, std::uint64_t startOffset)
49 , m_iterator(fileInfo.stream(), startOffset, fileInfo.size())
50 , m_validateChecksums(false)
51{
52}
53
57
59{
60 m_iterator.reset();
61}
62
74{
75 if (!target.tracks().empty()) {
76 // return the tag for the first matching track ID
77 for (auto &tag : m_tags) {
78 if (!tag->target().tracks().empty() && tag->target().tracks().front() == target.tracks().front() && !tag->oggParams().removed) {
79 return tag.get();
80 }
81 }
82 // not tag found -> try to re-use a tag which has been flagged as removed
83 for (auto &tag : m_tags) {
84 if (!tag->target().tracks().empty() && tag->target().tracks().front() == target.tracks().front()) {
85 tag->oggParams().removed = false;
86 return tag.get();
87 }
88 }
89 } else if (OggVorbisComment *comment = tag(0)) {
90 // no track ID specified -> just return the first tag (if one exists)
91 return comment;
92 } else if (!m_tags.empty()) {
93 // no track ID specified -> just return the first tag (try to re-use a tag which has been flagged as removed)
94 m_tags.front()->oggParams().removed = false;
95 return m_tags.front().get();
96 }
97
98 // a new tag needs to be created
99 // -> determine an appropriate track for the tag
100 // -> just use the first Vorbis/Opus track
101 for (const auto &track : m_tracks) {
102 if (target.tracks().empty() || target.tracks().front() == track->id()) {
103 switch (track->format().general) {
106 // check whether start page has a valid value
107 if (track->startPage() < m_iterator.pages().size()) {
108 announceComment(track->startPage(), numeric_limits<size_t>::max(), false, track->format().general);
109 m_tags.back()->setTarget(target);
110 return m_tags.back().get();
111 } else {
112 // TODO: error handling?
113 }
114 break;
115 default:;
116 }
117 // TODO: allow adding tags to FLAC tracks (not really important, because a tag should always be present)
118 }
119 }
120 return nullptr;
121}
122
124{
125 size_t i = 0;
126 for (const auto &tag : m_tags) {
127 if (!tag->oggParams().removed) {
128 if (index == i) {
129 return tag.get();
130 }
131 ++i;
132 }
133 }
134 return nullptr;
135}
136
138{
139 size_t count = 0;
140 for (const auto &tag : m_tags) {
141 if (!tag->oggParams().removed) {
142 ++count;
143 }
144 }
145 return count;
146}
147
158{
159 for (auto &existingTag : m_tags) {
160 if (static_cast<Tag *>(existingTag.get()) == tag) {
161 existingTag->removeAllFields();
162 existingTag->oggParams().removed = true;
163 return true;
164 }
165 }
166 return false;
167}
168
180{
181 for (auto &existingTag : m_tags) {
182 existingTag->removeAllFields();
183 existingTag->oggParams().removed = true;
184 }
185}
186
188{
189 CPP_UTILITIES_UNUSED(progress)
190
191 static const string context("parsing OGG bitstream header");
192 bool pagesSkipped = false, continueFromHere = false;
193
194 // iterate through pages using OggIterator helper class
195 try {
196 // ensure iterator is setup properly
197 for (m_iterator.removeFilter(), m_iterator.reset(); m_iterator;
198 continueFromHere ? [&] { continueFromHere = false; }() : m_iterator.nextPage()) {
199 progress.stopIfAborted();
200 const OggPage &page = m_iterator.currentPage();
201 if (m_validateChecksums && page.checksum() != OggPage::computeChecksum(stream(), page.startOffset())) {
202 diag.emplace_back(DiagLevel::Warning,
203 argsToString(
204 "The denoted checksum of the OGG page at ", m_iterator.currentSegmentOffset(), " does not match the computed checksum."),
205 context);
206 }
208 std::uint64_t lastNewStreamOffset = 0;
209 if (const auto streamIndex = m_streamsBySerialNo.find(page.streamSerialNumber()); streamIndex != m_streamsBySerialNo.end()) {
210 stream = m_tracks[streamIndex->second].get();
211 } else {
212 // new stream serial number recognized -> add new stream
213 m_streamsBySerialNo[page.streamSerialNumber()] = m_tracks.size();
214 m_tracks.emplace_back(make_unique<OggStream>(*this, m_iterator.currentPageIndex()));
215 stream = m_tracks.back().get();
216 lastNewStreamOffset = page.startOffset();
217 }
218 if (!pagesSkipped) {
219 stream->m_size += page.dataSize();
220 }
221 if (stream->m_currentSequenceNumber != page.sequenceNumber()) {
222 if (stream->m_currentSequenceNumber) {
223 diag.emplace_back(DiagLevel::Warning,
224 argsToString("Page of stream ", page.streamSerialNumber(), " missing; page sequence number ", stream->m_currentSequenceNumber,
225 " omitted at ", page.startOffset(), ", found ", page.sequenceNumber(), " instead."),
226 context);
227 }
228 stream->m_currentSequenceNumber = page.sequenceNumber() + 1;
229 } else {
230 ++stream->m_currentSequenceNumber;
231 }
232
233 // skip pages in the middle of a big file (still more than 100 MiB to parse) if no new track has been seen since the last 20 MiB
234 if (!fileInfo().isForcingFullParse() && (fileInfo().size() - page.startOffset()) > (100 * 0x100000)
235 && (page.startOffset() - lastNewStreamOffset) > (20 * 0x100000)) {
236 if (m_iterator.resyncAt(fileInfo().size() - (20 * 0x100000))) {
237 const OggPage &resyncedPage = m_iterator.currentPage();
238 // prevent warning about missing pages by resetting the sequence number of all streams and invalidate the stream size
239 for (auto &trackStream : m_tracks) {
240 trackStream->m_currentSequenceNumber = 0;
241 trackStream->m_size = 0;
242 }
243 pagesSkipped = continueFromHere = true;
244 diag.emplace_back(DiagLevel::Information,
245 argsToString("Pages in the middle of the file (", dataSizeToString(resyncedPage.startOffset() - page.startOffset()),
246 ") have been skipped to improve parsing speed. Hence track sizes can not be computed. Maybe not even all tracks could be "
247 "detected. Force a full parse to prevent this."),
248 context);
249 } else {
250 // abort if skipping pages didn't work
251 diag.emplace_back(DiagLevel::Critical,
252 "Unable to re-sync after skipping OGG pages in the middle of the file. Try forcing a full parse.", context);
253 return;
254 }
255 }
256 }
257 } catch (const TruncatedDataException &) {
258 // thrown when page exceeds max size
259 diag.emplace_back(DiagLevel::Critical, "The OGG file is truncated.", context);
260 } catch (const InvalidDataException &) {
261 // thrown when first 4 byte do not match capture pattern
262 const auto expectedOffset = m_iterator.currentSegmentOffset();
263 diag.emplace_back(DiagLevel::Critical, argsToString("Capture pattern \"OggS\" at ", expectedOffset, " expected."), context);
264 if (m_iterator.resyncAt(expectedOffset)) {
265 diag.emplace_back(DiagLevel::Warning,
266 argsToString("Found next capture pattern \"OggS\" at ", m_iterator.currentPageOffset(), ". Skipped ",
267 m_iterator.currentPageOffset() - expectedOffset, " invalid bytes."),
268 context);
269 continueFromHere = true;
270 } else {
271 diag.emplace_back(DiagLevel::Critical,
272 argsToString(
273 "Aborting after not being able to find any \"OggS\" capture patterns within 65307 bytes (from offset ", expectedOffset, ")."),
274 context);
275 }
276 }
277}
278
280{
281 // tracks needs to be parsed before because tags are stored at stream level
282 parseTracks(diag, progress);
283 for (auto &comment : m_tags) {
284 OggParameter &params = comment->oggParams();
285 m_iterator.setPageIndex(params.firstPageIndex);
286 m_iterator.setSegmentIndex(params.firstSegmentIndex);
287 m_iterator.setFilter(m_iterator.currentPage().streamSerialNumber());
288 switch (params.streamFormat) {
290 comment->parse(m_iterator, VorbisCommentFlags::None, diag);
291 break;
293 // skip header (has already been detected by OggStream)
294 m_iterator.ignore(8);
295 comment->parse(m_iterator, VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte, diag);
296 break;
298 m_iterator.ignore(4);
299 comment->parse(m_iterator, VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte, diag);
300 break;
301 default:
302 diag.emplace_back(DiagLevel::Critical, "Stream format not supported.", "parsing tags from OGG streams");
303 }
304 params.lastPageIndex = m_iterator.currentPageIndex();
305 params.lastSegmentIndex = m_iterator.currentSegmentIndex();
306 }
307}
308
320void OggContainer::announceComment(std::size_t pageIndex, std::size_t segmentIndex, bool lastMetaDataBlock, GeneralMediaFormat mediaFormat)
321{
322 auto &tag = m_tags.emplace_back(make_unique<OggVorbisComment>());
323 tag->oggParams().set(pageIndex, segmentIndex, lastMetaDataBlock, mediaFormat);
324 tag->target().tracks().emplace_back(m_iterator.pages()[pageIndex].streamSerialNumber());
325}
326
328{
329 static const string context("parsing OGG stream");
330 for (auto &stream : m_tracks) {
331 if (progress.isAborted()) {
333 }
334 try { // try to parse header
335 stream->parseHeader(diag, progress);
336 if (stream->duration() > m_duration) {
337 m_duration = stream->duration();
338 }
339 } catch (const Failure &) {
340 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse stream at ", stream->startOffset(), '.'), context);
341 }
342 }
343}
344
349void OggContainer::makeVorbisCommentSegment(stringstream &buffer, CopyHelper<65307> &copyHelper, vector<std::uint32_t> &newSegmentSizes,
350 VorbisComment *comment, OggParameter *params, Diagnostics &diag)
351{
352 const auto offset = buffer.tellp();
353 switch (params->streamFormat) {
355 comment->make(buffer, VorbisCommentFlags::None, diag);
356 break;
358 BE::getBytes(static_cast<std::uint64_t>(0x4F70757354616773u), copyHelper.buffer());
359 buffer.write(copyHelper.buffer(), 8);
361 break;
363 // Vorbis comment must be wrapped in "METADATA_BLOCK_HEADER"
365 header.setLast(params->lastMetaDataBlock);
367
368 // write the header later, when the size is known
369 buffer.write(copyHelper.buffer(), 4);
370
372
373 // finally make the header
374 header.setDataSize(static_cast<std::uint32_t>(buffer.tellp() - offset - 4));
375 if (header.dataSize() > 0xFFFFFF) {
376 diag.emplace_back(
377 DiagLevel::Critical, "Size of Vorbis comment exceeds size limit for FLAC \"METADATA_BLOCK_HEADER\".", "making Vorbis Comment");
378 }
379 buffer.seekp(offset);
380 header.makeHeader(buffer);
381 buffer.seekp(header.dataSize(), ios_base::cur);
382 break;
383 }
384 default:;
385 }
386 newSegmentSizes.push_back(static_cast<std::uint32_t>(buffer.tellp() - offset));
387}
388
390{
391 const string context("making OGG file");
392 progress.nextStepOrStop("Prepare for rewriting OGG file ...");
393 parseTags(diag, progress); // tags need to be parsed before the file can be rewritten
394 string originalPath = fileInfo().path(), backupPath;
395 NativeFileStream backupStream;
396
397 if (fileInfo().saveFilePath().empty()) {
398 // move current file to temp dir and reopen it as backupStream, recreate original file
399 try {
400 BackupHelper::createBackupFileCanonical(fileInfo().backupDirectory(), originalPath, backupPath, fileInfo().stream(), backupStream);
401 // recreate original file, define buffer variables
402 fileInfo().stream().open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
403 } catch (const std::ios_base::failure &failure) {
404 diag.emplace_back(
405 DiagLevel::Critical, argsToString("Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
406 throw;
407 }
408 } else {
409 // open the current file as backupStream and create a new outputStream at the specified "save file path"
410 try {
411 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
412 backupStream.open(BasicFileInfo::pathForOpen(fileInfo().path()).data(), ios_base::in | ios_base::binary);
413 fileInfo().close();
414 fileInfo().stream().open(
415 BasicFileInfo::pathForOpen(fileInfo().saveFilePath()).data(), ios_base::out | ios_base::binary | ios_base::trunc);
416 } catch (const std::ios_base::failure &failure) {
417 diag.emplace_back(DiagLevel::Critical, argsToString("Opening streams to write output file failed: ", failure.what()), context);
418 throw;
419 }
420 }
421
422 const auto totalFileSize = fileInfo().size();
423 try {
424 progress.nextStepOrStop("Writing OGG pages ...");
425
426 // prepare iterating comments
427 OggVorbisComment *currentComment;
428 OggParameter *currentParams;
429 auto tagIterator = m_tags.cbegin(), tagEnd = m_tags.cend();
430 if (tagIterator != tagEnd) {
431 currentParams = &(currentComment = tagIterator->get())->oggParams();
432 } else {
433 currentComment = nullptr;
434 currentParams = nullptr;
435 }
436
437 // define misc variables
438 CopyHelper<65307> copyHelper;
439 vector<std::uint64_t> updatedPageOffsets;
440 const OggPage *lastPage = nullptr;
441 std::uint64_t nextPageOffset;
442 unordered_map<std::uint32_t, std::uint32_t> pageSequenceNumberBySerialNo;
443
444 // iterate through all pages of the original file
445 auto updateTick = 0u;
446 for (m_iterator.setStream(backupStream), m_iterator.removeFilter(), m_iterator.reset(); m_iterator; m_iterator.nextPage(), ++updateTick) {
447 const OggPage &currentPage = m_iterator.currentPage();
448 if (updateTick % 10) {
449 progress.updateStepPercentage(static_cast<std::uint8_t>(currentPage.startOffset() * 100ul / totalFileSize));
450 progress.stopIfAborted();
451 }
452
453 // check for gaps
454 // note: This is not just to print diag messages but also for taking into account that the parser might skip pages
455 // unless a full parse has been enforced.
456 if (lastPage && currentPage.startOffset() != nextPageOffset) {
457 m_iterator.pages().resize(m_iterator.currentPageIndex() - 1); // drop all further pages after the last consecutively parsed one
458 if (m_iterator.resyncAt(nextPageOffset)) {
459 // try again at the page we've just found
460 const auto actuallyNextPageOffset = m_iterator.currentPageOffset();
461 if (actuallyNextPageOffset != nextPageOffset) {
462 diag.emplace_back(DiagLevel::Critical,
463 argsToString("Expected OGG page at offset ", nextPageOffset, " but found the next OGG page only at offset ",
464 actuallyNextPageOffset, ". Skipped ", (actuallyNextPageOffset - nextPageOffset), " invalid bytes."),
465 context);
466 nextPageOffset = actuallyNextPageOffset;
467 }
468 m_iterator.previousPage();
469 continue;
470 } else {
471 diag.emplace_back(DiagLevel::Critical,
472 argsToString(
473 "Expected OGG page at offset ", nextPageOffset, " but could not find any further pages. Skipped the rest of the file."),
474 context);
475 break;
476 }
477 }
478 const auto pageSize = currentPage.totalSize();
479 std::uint32_t &pageSequenceNumber = pageSequenceNumberBySerialNo[currentPage.streamSerialNumber()];
480 lastPage = &currentPage;
481 nextPageOffset = currentPage.startOffset() + pageSize;
482
483 // check whether the Vorbis Comment is present in this Ogg page
484 if (currentComment && m_iterator.currentPageIndex() >= currentParams->firstPageIndex
485 && m_iterator.currentPageIndex() <= currentParams->lastPageIndex && !currentPage.segmentSizes().empty()) {
486 // page needs to be rewritten (not just copied)
487 // -> write segments to a buffer first
488 stringstream buffer(ios_base::in | ios_base::out | ios_base::binary);
489 vector<std::uint32_t> newSegmentSizes;
490 newSegmentSizes.reserve(currentPage.segmentSizes().size());
491 std::uint64_t segmentOffset = m_iterator.currentSegmentOffset();
492 vector<std::uint32_t>::size_type segmentIndex = 0;
493 for (const auto segmentSize : currentPage.segmentSizes()) {
494 if (!segmentSize) {
495 ++segmentIndex;
496 continue;
497 }
498 // check whether this segment contains the Vorbis Comment
499 if (currentParams
500 && (m_iterator.currentPageIndex() >= currentParams->firstPageIndex && segmentIndex >= currentParams->firstSegmentIndex)
501 && (m_iterator.currentPageIndex() <= currentParams->lastPageIndex && segmentIndex <= currentParams->lastSegmentIndex)) {
502 // prevent making the comment twice if it spreads over multiple pages/segments
503 if (!currentParams->removed
504 && ((m_iterator.currentPageIndex() == currentParams->firstPageIndex
505 && m_iterator.currentSegmentIndex() == currentParams->firstSegmentIndex))) {
506 makeVorbisCommentSegment(buffer, copyHelper, newSegmentSizes, currentComment, currentParams, diag);
507 }
508
509 // proceed with next comment?
510 if (m_iterator.currentPageIndex() > currentParams->lastPageIndex
511 || (m_iterator.currentPageIndex() == currentParams->lastPageIndex && segmentIndex > currentParams->lastSegmentIndex)) {
512 if (++tagIterator != tagEnd) {
513 currentParams = &(currentComment = tagIterator->get())->oggParams();
514 } else {
515 currentComment = nullptr;
516 currentParams = nullptr;
517 }
518 }
519 } else {
520 // copy other segments unchanged
521 backupStream.seekg(static_cast<streamoff>(segmentOffset));
522 copyHelper.copy(backupStream, buffer, segmentSize);
523 newSegmentSizes.push_back(segmentSize);
524
525 // check whether there is a new comment to be inserted into the current page
526 if (currentParams && m_iterator.currentPageIndex() == currentParams->lastPageIndex
527 && currentParams->firstSegmentIndex == numeric_limits<size_t>::max()) {
528 if (!currentParams->removed) {
529 makeVorbisCommentSegment(buffer, copyHelper, newSegmentSizes, currentComment, currentParams, diag);
530 }
531 // proceed with next comment
532 if (++tagIterator != tagEnd) {
533 currentParams = &(currentComment = tagIterator->get())->oggParams();
534 } else {
535 currentComment = nullptr;
536 currentParams = nullptr;
537 }
538 }
539 }
540 segmentOffset += segmentSize;
541 ++segmentIndex;
542 }
543
544 // write buffered data to actual stream
545 if (auto newSegmentSizesIterator = newSegmentSizes.cbegin(), newSegmentSizesEnd = newSegmentSizes.cend();
546 newSegmentSizesIterator != newSegmentSizesEnd) {
547 auto bytesLeft = *newSegmentSizesIterator;
548 auto continuePreviousSegment = false, needsZeroLacingValue = false;
549 // write pages until all data in the buffer is written
550 while (newSegmentSizesIterator != newSegmentSizesEnd) {
551 // write page header
552 backupStream.seekg(static_cast<streamoff>(currentPage.startOffset()));
553 updatedPageOffsets.push_back(static_cast<std::uint64_t>(stream().tellp())); // memorize offset to update checksum later
554 copyHelper.copy(backupStream, stream(), 27); // just copy header from original file
555 // set continue flag
556 stream().seekp(-22, ios_base::cur);
557 stream().put(static_cast<char>(currentPage.headerTypeFlag() & (continuePreviousSegment ? 0xFF : 0xFE)));
558 continuePreviousSegment = true;
559 // adjust page sequence number
560 stream().seekp(12, ios_base::cur);
561 writer().writeUInt32LE(pageSequenceNumber);
562 stream().seekp(5, ios_base::cur);
563 std::int16_t segmentSizesWritten = 0; // in the current page header only
564 // write segment sizes as long as there are segment sizes to be written and
565 // the max number of segment sizes (255) is not exceeded
566 std::uint32_t currentSize = 0;
567 while ((bytesLeft || needsZeroLacingValue) && segmentSizesWritten < 0xFF) {
568 while (bytesLeft > 0xFF && segmentSizesWritten < 0xFF) {
569 stream().put(static_cast<char>(0xFF));
570 currentSize += 0xFF;
571 bytesLeft -= 0xFF;
572 ++segmentSizesWritten;
573 }
574 if ((bytesLeft || needsZeroLacingValue) && segmentSizesWritten < 0xFF) {
575 // bytes left is here <= 0xFF
576 stream().put(static_cast<char>(bytesLeft));
577 currentSize += bytesLeft;
578 needsZeroLacingValue = bytesLeft == 0xFF;
579 bytesLeft = 0;
580 ++segmentSizesWritten;
581 }
582 if (!bytesLeft && !needsZeroLacingValue) {
583 // sizes for the segment have been written
584 // -> continue with next segment
585 if (++newSegmentSizesIterator != newSegmentSizesEnd) {
586 bytesLeft = *newSegmentSizesIterator;
587 continuePreviousSegment = false;
588 }
589 }
590 }
591
592 // there are no bytes left in the current segment; remove continue flag
593 if (!bytesLeft && !needsZeroLacingValue) {
594 continuePreviousSegment = false;
595 }
596
597 // page is full or all segment data has been covered
598 // -> write segment table size (segmentSizesWritten) and segment data
599 // -> seek back and write updated page segment number
600 stream().seekp(-1 - segmentSizesWritten, ios_base::cur);
601 stream().put(static_cast<char>(segmentSizesWritten));
602 stream().seekp(segmentSizesWritten, ios_base::cur);
603 // -> write actual page data
604 copyHelper.copy(buffer, stream(), currentSize);
605
606 ++pageSequenceNumber;
607 }
608 }
609
610 } else {
611 if (pageSequenceNumber != m_iterator.currentPageIndex()) {
612 // just update page sequence number
613 backupStream.seekg(static_cast<streamoff>(currentPage.startOffset()));
614 updatedPageOffsets.push_back(static_cast<std::uint64_t>(stream().tellp())); // memorize offset to update checksum later
615 copyHelper.copy(backupStream, stream(), 27);
616 stream().seekp(-9, ios_base::cur);
617 writer().writeUInt32LE(pageSequenceNumber);
618 stream().seekp(5, ios_base::cur);
619 copyHelper.copy(backupStream, stream(), pageSize - 27);
620 } else {
621 // copy page unchanged
622 backupStream.seekg(static_cast<streamoff>(currentPage.startOffset()));
623 copyHelper.copy(backupStream, stream(), pageSize);
624 }
625 ++pageSequenceNumber;
626 }
627 }
628
629 // report new size
630 fileInfo().reportSizeChanged(static_cast<std::uint64_t>(stream().tellp()));
631 progress.updateStepPercentage(100);
632
633 // "save as path" is now the regular path
634 if (!fileInfo().saveFilePath().empty()) {
635 fileInfo().reportPathChanged(fileInfo().saveFilePath());
636 fileInfo().setSaveFilePath(string());
637 }
638
639 // close backups stream; reopen new file as readable stream
640 backupStream.close();
641 fileInfo().close();
642 fileInfo().stream().open(BasicFileInfo::pathForOpen(fileInfo().path()).data(), ios_base::in | ios_base::out | ios_base::binary);
643
644 // update checksums of modified pages
645 progress.nextStepOrStop("Updating checksums ...");
646 updateTick = 0u;
647 for (auto offset : updatedPageOffsets) {
648 if (updateTick++ % 10) {
649 progress.updateStepPercentage(static_cast<std::uint8_t>(offset * 100ul / fileInfo().size()));
650 progress.stopIfAborted();
651 }
653 }
654
655 // prevent deferring final write operations (to catch and handle possible errors here)
656 fileInfo().stream().flush();
657 progress.updateStepPercentage(100);
658
659 // clear iterator
660 m_iterator.clear(fileInfo().stream(), startOffset(), fileInfo().size());
661
662 } catch (...) {
663 m_iterator.setStream(fileInfo().stream());
664 BackupHelper::handleFailureAfterFileModifiedCanonical(fileInfo(), originalPath, backupPath, fileInfo().stream(), backupStream, diag, context);
665 }
666}
667
668} // namespace TagParser
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks.
bool isAborted() const
Returns whether the operation has been aborted via tryToAbort().
void stopIfAborted() const
Throws an OperationAbortedException if aborted.
void nextStepOrStop(const std::string &step, std::uint8_t stepPercentage=0)
Throws an OperationAbortedException if aborted; otherwise the data for the next step is set.
std::iostream & stream()
Returns the related stream.
std::uint64_t startOffset() const
Returns the start offset in the related stream.
void parseTracks(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the tracks of the file if not parsed yet.
CppUtilities::BinaryWriter & writer()
Returns the related BinaryWriter.
void parseTags(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the tag information if not parsed yet.
CppUtilities::TimeSpan m_duration
std::uint64_t id() const
Returns the track ID if known; otherwise returns 0.
MediaFormat format() const
Returns the format of the track if known; otherwise returns MediaFormat::Unknown.
void reportPathChanged(std::string_view newPath)
Call this function to report that the path changed.
const std::string & path() const
Returns the path of the current file.
std::uint64_t size() const
Returns size of the current file in bytes.
CppUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
void close()
A possibly opened std::fstream will be closed.
static std::string_view pathForOpen(std::string_view url)
Returns removes the "file:/" prefix from url to be able to pass it to functions like open(),...
void reportSizeChanged(std::uint64_t newSize)
Call this function to report that the size changed.
void updateStepPercentage(std::uint8_t stepPercentage)
Updates the current step percentage and invokes the second callback specified on construction (or the...
The Diagnostics class is a container for DiagMessage.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition exceptions.h:11
void removeAllFields()
Removes all fields from the tag.
The FlacMetaDataBlockHeader class is a FLAC "METADATA_BLOCK_HEADER" parser and maker.
constexpr std::uint32_t dataSize() const
Returns the length in bytes of the meta data (excluding the size of the header itself).
void setType(FlacMetaDataBlockType type)
Sets the block type.
void setLast(std::uint8_t last)
Sets whether this is the last metadata block before the audio blocks.
void setDataSize(std::uint32_t dataSize)
Sets the length in bytes of the meta data (excluding the size of the header itself).
void makeHeader(std::ostream &outputStream)
Writes the header to the specified outputStream.
The GenericContainer class helps parsing header, track, tag and chapter information of a file.
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition exceptions.h:25
The MediaFileInfo class allows to read and write tag information providing a container/tag format ind...
void setSaveFilePath(std::string_view saveFilePath)
Sets the "save file path".
bool isForcingFullParse() const
Returns an indication whether forcing a full parse is enabled.
GeneralMediaFormat general
void internalParseTags(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the tags.
OggVorbisComment * tag(std::size_t index) override
Returns the tag with the specified index.
std::size_t tagCount() const override
Returns the number of tags attached to the container.
void internalParseTracks(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the tracks.
OggContainer(MediaFileInfo &fileInfo, std::uint64_t startOffset)
Constructs a new container for the specified stream at the specified startOffset.
void internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the header.
void removeAllTags() override
Actually just flags all tags as removed and clears all assigned fields.
OggVorbisComment * createTag(const TagTarget &target) override
Creates a new tag.
void reset() override
Discards all parsing results.
void internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to make the file.
bool removeTag(Tag *tag) override
Actually just flags the specified tag as removed and clears all assigned fields.
void nextPage()
Increases the current position by one page.
void setSegmentIndex(std::vector< std::uint32_t >::size_type index)
Sets the current segment index.
void removeFilter()
Removes a previously set filter.
void setStream(std::istream &stream)
Sets the stream.
std::vector< std::uint32_t >::size_type currentSegmentIndex() const
Returns the index of the current segment (in the current page) if the iterator is valid; otherwise an...
void clear(std::istream &stream, std::uint64_t startOffset, std::uint64_t streamSize)
Sets the stream and related parameters and clears all available pages.
void previousPage()
Decreases the current position by one page.
std::uint64_t currentSegmentOffset() const
Returns the start offset of the current segment in the input stream if the iterator is valid; otherwi...
std::uint64_t currentPageOffset() const
Returns the start offset of the current OGG page.
const OggPage & currentPage() const
Returns the current OGG page.
const std::vector< OggPage > & pages() const
Returns a vector of containing the OGG pages that have been fetched yet.
bool resyncAt(std::uint64_t offset)
Fetches the next page at the specified offset.
void setPageIndex(std::vector< OggPage >::size_type index)
Sets the current page index.
std::vector< OggPage >::size_type currentPageIndex() const
Returns the index of the current page if the iterator is valid; otherwise an undefined index is retur...
void reset()
Resets the iterator to point at the first segment of the first page (matching the filter if set).
void setFilter(std::uint32_t streamSerialId)
Allows to filter pages by the specified streamSerialId.
void ignore(std::size_t count=1)
Advances the position of the next character to be read from the OGG stream by count bytes.
The OggPage class is used to parse OGG pages.
Definition oggpage.h:13
std::uint32_t sequenceNumber() const
Returns the page sequence number.
Definition oggpage.h:185
std::uint32_t dataSize() const
Returns the data size in byte.
Definition oggpage.h:238
std::uint32_t totalSize() const
Returns the total size of the page in byte.
Definition oggpage.h:246
std::uint32_t streamSerialNumber() const
Returns the stream serial number.
Definition oggpage.h:166
const std::vector< std::uint32_t > & segmentSizes() const
Returns the sizes of the segments of the page in byte.
Definition oggpage.h:220
std::uint8_t headerTypeFlag() const
Returns the header type flag.
Definition oggpage.h:105
std::uint64_t startOffset() const
Returns the start offset of the page.
Definition oggpage.h:86
std::uint32_t checksum() const
Returns the page checksum.
Definition oggpage.h:200
static std::uint32_t computeChecksum(std::istream &stream, std::uint64_t startOffset)
Computes the actual checksum of the page read from the specified stream at the specified startOffset.
Definition oggpage.cpp:79
static void updateChecksum(std::iostream &stream, std::uint64_t startOffset)
Updates the checksum of the page read from the specified stream at the specified startOffset.
Definition oggpage.cpp:116
Implementation of TagParser::AbstractTrack for OGG streams.
Definition oggstream.h:13
std::size_t startPage() const
Definition oggstream.h:34
Specialization of TagParser::VorbisComment for Vorbis comments inside an OGG stream.
OggParameter & oggParams()
Returns the OGG parameter for the comment.
std::string_view typeName() const override
Returns the type name of the tag as C-style string.
The exception that is thrown when an operation has been stopped and thus not successfully completed b...
Definition exceptions.h:46
The TagTarget class specifies the target of a tag.
Definition tagtarget.h:20
const IdContainerType & tracks() const
Returns the tracks.
Definition tagtarget.h:105
The Tag class is used to store, read and write tag information.
Definition tag.h:166
const TagTarget & target() const
Definition tag.h:244
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition exceptions.h:39
Implementation of TagParser::Tag for Vorbis comments.
TAG_PARSER_EXPORT void handleFailureAfterFileModifiedCanonical(MediaFileInfo &fileInfo, const std::string &originalPath, const std::string &backupPath, CppUtilities::NativeFileStream &outputStream, CppUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
Handles a failure/abort which occurred after the file has been modified.
TAG_PARSER_EXPORT void createBackupFileCanonical(const std::string &backupDir, std::string &originalPath, std::string &backupPath, CppUtilities::NativeFileStream &originalStream, CppUtilities::NativeFileStream &backupStream)
Creates a backup file like createBackupFile() but canonicalizes originalPath before doing the backup.
Contains all classes and functions of the TagInfo library.
Definition aaccodebook.h:10
GeneralMediaFormat
The GeneralMediaFormat enum specifies the general format of media data (PCM, MPEG-4,...
Definition mediaformat.h:30
The OggParameter struct holds the OGG parameter for a VorbisComment.
GeneralMediaFormat streamFormat
std::size_t lastSegmentIndex
std::size_t firstSegmentIndex
void set(std::size_t pageIndex, std::size_t segmentIndex, bool lastMetaDataBlock, GeneralMediaFormat streamFormat=GeneralMediaFormat::Vorbis)
Sets the firstPageIndex/lastPageIndex, the firstSegmentIndex/lastSegmentIndex, whether the associated...