Tag Parser  10.1.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 
14 using namespace std;
15 using namespace CppUtilities;
16 
17 namespace TagParser {
18 
24 std::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)";
31  case GeneralMediaFormat::Theora:
32  return "Vorbis comment (in Theora stream)";
33  default:
34  return "Vorbis comment";
35  }
36 }
37 
46 OggContainer::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  }
206  OggStream *stream;
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 &stream : m_tracks) {
239  stream->m_currentSequenceNumber = 0;
240  stream->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 
318 void 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 
346 void 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 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::createBackupFile(fileInfo().backupDirectory(), fileInfo().path(), backupPath, fileInfo().stream(), backupStream);
398  // recreate original file, define buffer variables
399  fileInfo().stream().open(BasicFileInfo::pathForOpen(fileInfo().path()).data(), 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::handleFailureAfterFileModified(fileInfo(), 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.
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:74
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.
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
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:104
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 createBackupFile(const std::string &backupDir, const std::string &originalPath, std::string &backupPath, CppUtilities::NativeFileStream &originalStream, CppUtilities::NativeFileStream &backupStream)
TAG_PARSER_EXPORT void handleFailureAfterFileModified(MediaFileInfo &mediaFileInfo, const std::string &backupPath, CppUtilities::NativeFileStream &outputStream, CppUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
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