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