Tag Parser  7.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/catchiofailure.h>
11 #include <c++utilities/io/copy.h>
12 
13 #include <memory>
14 
15 using namespace std;
16 using namespace IoUtilities;
17 using namespace ConversionUtilities;
18 
19 namespace TagParser {
20 
26 const char *OggVorbisComment::typeName() const
27 {
28  switch (m_oggParams.streamFormat) {
30  return "Vorbis comment (in FLAC stream)";
31  case GeneralMediaFormat::Opus:
32  return "Vorbis comment (in Opus stream)";
33  case GeneralMediaFormat::Theora:
34  return "Vorbis comment (in Theora stream)";
35  default:
36  return "Vorbis comment";
37  }
38 }
39 
48 OggContainer::OggContainer(MediaFileInfo &fileInfo, uint64 startOffset)
50  , m_iterator(fileInfo.stream(), startOffset, fileInfo.size())
51  , m_validateChecksums(false)
52 {
53 }
54 
56 {
57 }
58 
60 {
61  m_iterator.reset();
62 }
63 
75 {
76  if (!target.tracks().empty()) {
77  // return the tag for the first matching track ID
78  for (auto &tag : m_tags) {
79  if (!tag->target().tracks().empty() && tag->target().tracks().front() == target.tracks().front() && !tag->oggParams().removed) {
80  return tag.get();
81  }
82  }
83  // not tag found -> try to re-use a tag which has been flagged as removed
84  for (auto &tag : m_tags) {
85  if (!tag->target().tracks().empty() && tag->target().tracks().front() == target.tracks().front()) {
86  tag->oggParams().removed = false;
87  return tag.get();
88  }
89  }
90  } else if (OggVorbisComment *comment = tag(0)) {
91  // no track ID specified -> just return the first tag (if one exists)
92  return comment;
93  } else if (!m_tags.empty()) {
94  // no track ID specified -> just return the first tag (try to re-use a tag which has been flagged as removed)
95  m_tags.front()->oggParams().removed = false;
96  return m_tags.front().get();
97  }
98 
99  // a new tag needs to be created
100  // -> determine an appropriate track for the tag
101  // -> just use the first Vorbis/Opus track
102  for (const auto &track : m_tracks) {
103  if (target.tracks().empty() || target.tracks().front() == track->id()) {
104  switch (track->format().general) {
107  // check whether start page has a valid value
108  if (track->startPage() < m_iterator.pages().size()) {
109  announceComment(track->startPage(), numeric_limits<size_t>::max(), false, track->format().general);
110  m_tags.back()->setTarget(target);
111  return m_tags.back().get();
112  } else {
113  // TODO: error handling?
114  }
115  break;
116  default:;
117  }
118  // TODO: allow adding tags to FLAC tracks (not really important, because a tag should always be present)
119  }
120  }
121  return nullptr;
122 }
123 
125 {
126  size_t i = 0;
127  for (const auto &tag : m_tags) {
128  if (!tag->oggParams().removed) {
129  if (index == i) {
130  return tag.get();
131  }
132  ++i;
133  }
134  }
135  return nullptr;
136 }
137 
139 {
140  size_t count = 0;
141  for (const auto &tag : m_tags) {
142  if (!tag->oggParams().removed) {
143  ++count;
144  }
145  }
146  return count;
147 }
148 
159 {
160  for (auto &existingTag : m_tags) {
161  if (static_cast<Tag *>(existingTag.get()) == tag) {
162  existingTag->removeAllFields();
163  existingTag->oggParams().removed = true;
164  return true;
165  }
166  }
167  return false;
168 }
169 
181 {
182  for (auto &existingTag : m_tags) {
183  existingTag->removeAllFields();
184  existingTag->oggParams().removed = true;
185  }
186 }
187 
189 {
190  static const string context("parsing OGG bitstream header");
191  bool pagesSkipped = 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; m_iterator.nextPage()) {
197  const OggPage &page = m_iterator.currentPage();
198  if (m_validateChecksums && page.checksum() != OggPage::computeChecksum(stream(), page.startOffset())) {
199  diag.emplace_back(DiagLevel::Warning,
200  argsToString(
201  "The denoted checksum of the OGG page at ", m_iterator.currentSegmentOffset(), " does not match the computed checksum."),
202  context);
203  }
204  OggStream *stream;
205  uint64 lastNewStreamOffset = 0;
206  try {
207  stream = m_tracks[m_streamsBySerialNo.at(page.streamSerialNumber())].get();
208  stream->m_size += page.dataSize();
209  } catch (const out_of_range &) {
210  // new stream serial number recognized -> add new stream
211  m_streamsBySerialNo[page.streamSerialNumber()] = m_tracks.size();
212  m_tracks.emplace_back(make_unique<OggStream>(*this, m_iterator.currentPageIndex()));
213  stream = m_tracks.back().get();
214  lastNewStreamOffset = page.startOffset();
215  }
216  if (stream->m_currentSequenceNumber != page.sequenceNumber()) {
217  if (stream->m_currentSequenceNumber) {
218  diag.emplace_back(DiagLevel::Warning, "Page is missing (page sequence number omitted).", context);
219  }
220  stream->m_currentSequenceNumber = page.sequenceNumber() + 1;
221  } else {
222  ++stream->m_currentSequenceNumber;
223  }
224 
225  // 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
226  if (!fileInfo().isForcingFullParse() && (fileInfo().size() - page.startOffset()) > (100 * 0x100000)
227  && (page.startOffset() - lastNewStreamOffset) > (20 * 0x100000)) {
228  if (m_iterator.resyncAt(fileInfo().size() - (20 * 0x100000))) {
229  const OggPage &resyncedPage = m_iterator.currentPage();
230  // prevent warning about missing pages
231  stream->m_currentSequenceNumber = resyncedPage.sequenceNumber() + 1;
232  pagesSkipped = true;
233  diag.emplace_back(DiagLevel::Information,
234  argsToString("Pages in the middle of the file (", dataSizeToString(resyncedPage.startOffset() - page.startOffset()),
235  ") have been skipped to improve parsing speed. Hence track sizes can not be computed. Maybe not even all tracks could be "
236  "detected. Force a full parse to prevent this."),
237  context);
238  } else {
239  // abort if skipping pages didn't work
240  diag.emplace_back(DiagLevel::Critical,
241  "Unable to re-sync after skipping OGG pages in the middle of the file. Try forcing a full parse.", context);
242  return;
243  }
244  }
245  }
246  } catch (const TruncatedDataException &) {
247  // thrown when page exceeds max size
248  diag.emplace_back(DiagLevel::Critical, "The OGG file is truncated.", context);
249  } catch (const InvalidDataException &) {
250  // thrown when first 4 byte do not match capture pattern
251  diag.emplace_back(
252  DiagLevel::Critical, argsToString("Capture pattern \"OggS\" at ", m_iterator.currentSegmentOffset(), " expected."), context);
253  }
254 
255  // invalidate stream sizes in case pages have been skipped
256  if (pagesSkipped) {
257  for (auto &stream : m_tracks) {
258  stream->m_size = 0;
259  }
260  }
261 }
262 
264 {
265  // tracks needs to be parsed before because tags are stored at stream level
266  parseTracks(diag);
267  for (auto &comment : m_tags) {
268  OggParameter &params = comment->oggParams();
269  m_iterator.setPageIndex(params.firstPageIndex);
270  m_iterator.setSegmentIndex(params.firstSegmentIndex);
271  switch (params.streamFormat) {
273  comment->parse(m_iterator, VorbisCommentFlags::None, diag);
274  break;
276  // skip header (has already been detected by OggStream)
277  m_iterator.ignore(8);
279  break;
281  m_iterator.ignore(4);
283  break;
284  default:
285  diag.emplace_back(DiagLevel::Critical, "Stream format not supported.", "parsing tags from OGG streams");
286  }
287  params.lastPageIndex = m_iterator.currentPageIndex();
288  params.lastSegmentIndex = m_iterator.currentSegmentIndex();
289  }
290 }
291 
303 void OggContainer::announceComment(std::size_t pageIndex, std::size_t segmentIndex, bool lastMetaDataBlock, GeneralMediaFormat mediaFormat)
304 {
305  m_tags.emplace_back(make_unique<OggVorbisComment>());
306  m_tags.back()->oggParams().set(pageIndex, segmentIndex, lastMetaDataBlock, mediaFormat);
307 }
308 
310 {
311  static const string context("parsing OGG stream");
312  for (auto &stream : m_tracks) {
313  try { // try to parse header
314  stream->parseHeader(diag);
315  if (stream->duration() > m_duration) {
316  m_duration = stream->duration();
317  }
318  } catch (const Failure &) {
319  diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse stream at ", stream->startOffset(), '.'), context);
320  }
321  }
322 }
323 
328 void OggContainer::makeVorbisCommentSegment(stringstream &buffer, CopyHelper<65307> &copyHelper, vector<uint32> &newSegmentSizes,
330 {
331  const auto offset = buffer.tellp();
332  switch (params->streamFormat) {
334  comment->make(buffer, VorbisCommentFlags::None, diag);
335  break;
337  ConversionUtilities::BE::getBytes(static_cast<uint64>(0x4F70757354616773u), copyHelper.buffer());
338  buffer.write(copyHelper.buffer(), 8);
340  break;
342  // Vorbis comment must be wrapped in "METADATA_BLOCK_HEADER"
344  header.setLast(params->lastMetaDataBlock);
346 
347  // write the header later, when the size is known
348  buffer.write(copyHelper.buffer(), 4);
349 
351 
352  // finally make the header
353  header.setDataSize(static_cast<uint32>(buffer.tellp() - offset - 4));
354  if (header.dataSize() > 0xFFFFFF) {
355  diag.emplace_back(
356  DiagLevel::Critical, "Size of Vorbis comment exceeds size limit for FLAC \"METADATA_BLOCK_HEADER\".", "making Vorbis Comment");
357  }
358  buffer.seekp(offset);
359  header.makeHeader(buffer);
360  buffer.seekp(header.dataSize(), ios_base::cur);
361  break;
362  }
363  default:;
364  }
365  newSegmentSizes.push_back(static_cast<uint32>(buffer.tellp() - offset));
366 }
367 
369 {
370  const string context("making OGG file");
371  progress.updateStep("Prepare for rewriting OGG file ...");
372  parseTags(diag); // tags need to be parsed before the file can be rewritten
373  string backupPath;
374  NativeFileStream backupStream;
375 
376  if (fileInfo().saveFilePath().empty()) {
377  // move current file to temp dir and reopen it as backupStream, recreate original file
378  try {
379  BackupHelper::createBackupFile(fileInfo().path(), backupPath, fileInfo().stream(), backupStream);
380  // recreate original file, define buffer variables
381  fileInfo().stream().open(fileInfo().path(), ios_base::out | ios_base::binary | ios_base::trunc);
382  } catch (...) {
383  const char *what = catchIoFailure();
384  diag.emplace_back(DiagLevel::Critical, "Creation of temporary file (to rewrite the original file) failed.", context);
385  throwIoFailure(what);
386  }
387  } else {
388  // open the current file as backupStream and create a new outputStream at the specified "save file path"
389  try {
390  backupStream.exceptions(ios_base::badbit | ios_base::failbit);
391  backupStream.open(fileInfo().path(), ios_base::in | ios_base::binary);
392  fileInfo().close();
393  fileInfo().stream().open(fileInfo().saveFilePath(), ios_base::out | ios_base::binary | ios_base::trunc);
394  } catch (...) {
395  const char *what = catchIoFailure();
396  diag.emplace_back(DiagLevel::Critical, "Opening streams to write output file failed.", context);
397  throwIoFailure(what);
398  }
399  }
400 
401  try {
402  // prepare iterating comments
403  OggVorbisComment *currentComment;
404  OggParameter *currentParams;
405  auto tagIterator = m_tags.cbegin(), tagEnd = m_tags.cend();
406  if (tagIterator != tagEnd) {
407  currentParams = &(currentComment = tagIterator->get())->oggParams();
408  } else {
409  currentComment = nullptr;
410  currentParams = nullptr;
411  }
412 
413  // define misc variables
414  CopyHelper<65307> copyHelper;
415  vector<uint64> updatedPageOffsets;
416  unordered_map<uint32, uint32> pageSequenceNumberBySerialNo;
417 
418  // iterate through all pages of the original file
419  for (m_iterator.setStream(backupStream), m_iterator.removeFilter(), m_iterator.reset(); m_iterator; m_iterator.nextPage()) {
420  const OggPage &currentPage = m_iterator.currentPage();
421  const auto pageSize = currentPage.totalSize();
422  uint32 &pageSequenceNumber = pageSequenceNumberBySerialNo[currentPage.streamSerialNumber()];
423  // check whether the Vorbis Comment is present in this Ogg page
424  if (currentComment && m_iterator.currentPageIndex() >= currentParams->firstPageIndex
425  && m_iterator.currentPageIndex() <= currentParams->lastPageIndex && !currentPage.segmentSizes().empty()) {
426  // page needs to be rewritten (not just copied)
427  // -> write segments to a buffer first
428  stringstream buffer(ios_base::in | ios_base::out | ios_base::binary);
429  vector<uint32> newSegmentSizes;
430  newSegmentSizes.reserve(currentPage.segmentSizes().size());
431  uint64 segmentOffset = m_iterator.currentSegmentOffset();
432  vector<uint32>::size_type segmentIndex = 0;
433  for (const auto segmentSize : currentPage.segmentSizes()) {
434  if (!segmentSize) {
435  ++segmentIndex;
436  continue;
437  }
438  // check whether this segment contains the Vorbis Comment
439  if ((m_iterator.currentPageIndex() >= currentParams->firstPageIndex && segmentIndex >= currentParams->firstSegmentIndex)
440  && (m_iterator.currentPageIndex() <= currentParams->lastPageIndex && segmentIndex <= currentParams->lastSegmentIndex)) {
441  // prevent making the comment twice if it spreads over multiple pages/segments
442  if (!currentParams->removed
443  && ((m_iterator.currentPageIndex() == currentParams->firstPageIndex
444  && m_iterator.currentSegmentIndex() == currentParams->firstSegmentIndex))) {
445  makeVorbisCommentSegment(buffer, copyHelper, newSegmentSizes, currentComment, currentParams, diag);
446  }
447 
448  // proceed with next comment?
449  if (m_iterator.currentPageIndex() > currentParams->lastPageIndex
450  || (m_iterator.currentPageIndex() == currentParams->lastPageIndex && segmentIndex > currentParams->lastSegmentIndex)) {
451  if (++tagIterator != tagEnd) {
452  currentParams = &(currentComment = tagIterator->get())->oggParams();
453  } else {
454  currentComment = nullptr;
455  currentParams = nullptr;
456  }
457  }
458  } else {
459  // copy other segments unchanged
460  backupStream.seekg(static_cast<streamoff>(segmentOffset));
461  copyHelper.copy(backupStream, buffer, segmentSize);
462  newSegmentSizes.push_back(segmentSize);
463 
464  // check whether there is a new comment to be inserted into the current page
465  if (m_iterator.currentPageIndex() == currentParams->lastPageIndex
466  && currentParams->firstSegmentIndex == numeric_limits<size_t>::max()) {
467  if (!currentParams->removed) {
468  makeVorbisCommentSegment(buffer, copyHelper, newSegmentSizes, currentComment, currentParams, diag);
469  }
470  // proceed with next comment
471  if (++tagIterator != tagEnd) {
472  currentParams = &(currentComment = tagIterator->get())->oggParams();
473  } else {
474  currentComment = nullptr;
475  currentParams = nullptr;
476  }
477  }
478  }
479  segmentOffset += segmentSize;
480  ++segmentIndex;
481  }
482 
483  // write buffered data to actual stream
484  auto newSegmentSizesIterator = newSegmentSizes.cbegin(), newSegmentSizesEnd = newSegmentSizes.cend();
485  bool continuePreviousSegment = false;
486  if (newSegmentSizesIterator != newSegmentSizesEnd) {
487  uint32 bytesLeft = *newSegmentSizesIterator;
488  // write pages until all data in the buffer is written
489  while (newSegmentSizesIterator != newSegmentSizesEnd) {
490  // write header
491  backupStream.seekg(static_cast<streamoff>(currentPage.startOffset()));
492  updatedPageOffsets.push_back(static_cast<uint64>(stream().tellp())); // memorize offset to update checksum later
493  copyHelper.copy(backupStream, stream(), 27); // just copy header from original file
494  // set continue flag
495  stream().seekp(-22, ios_base::cur);
496  stream().put(static_cast<char>(currentPage.headerTypeFlag() & (continuePreviousSegment ? 0xFF : 0xFE)));
497  continuePreviousSegment = true;
498  // adjust page sequence number
499  stream().seekp(12, ios_base::cur);
500  writer().writeUInt32LE(pageSequenceNumber);
501  stream().seekp(5, ios_base::cur);
502  int16 segmentSizesWritten = 0; // in the current page header only
503  // write segment sizes as long as there are segment sizes to be written and
504  // the max number of segment sizes (255) is not exceeded
505  uint32 currentSize = 0;
506  while (bytesLeft && segmentSizesWritten < 0xFF) {
507  while (bytesLeft >= 0xFF && segmentSizesWritten < 0xFF) {
508  stream().put(static_cast<char>(0xFF));
509  currentSize += 0xFF;
510  bytesLeft -= 0xFF;
511  ++segmentSizesWritten;
512  }
513  if (bytesLeft && segmentSizesWritten < 0xFF) {
514  // bytes left is here < 0xFF
515  stream().put(static_cast<char>(bytesLeft));
516  currentSize += bytesLeft;
517  bytesLeft = 0;
518  ++segmentSizesWritten;
519  }
520  if (!bytesLeft) {
521  // sizes for the segment have been written
522  // -> continue with next segment
523  if (++newSegmentSizesIterator != newSegmentSizesEnd) {
524  bytesLeft = *newSegmentSizesIterator;
525  continuePreviousSegment = false;
526  }
527  }
528  }
529 
530  // there are no bytes left in the current segment; remove continue flag
531  if (!bytesLeft) {
532  continuePreviousSegment = false;
533  }
534 
535  // page is full or all segment data has been covered
536  // -> write segment table size (segmentSizesWritten) and segment data
537  // -> seek back and write updated page segment number
538  stream().seekp(-1 - segmentSizesWritten, ios_base::cur);
539  stream().put(static_cast<char>(segmentSizesWritten));
540  stream().seekp(segmentSizesWritten, ios_base::cur);
541  // -> write actual page data
542  copyHelper.copy(buffer, stream(), currentSize);
543 
544  ++pageSequenceNumber;
545  }
546  }
547 
548  } else {
549  if (pageSequenceNumber != m_iterator.currentPageIndex()) {
550  // just update page sequence number
551  backupStream.seekg(static_cast<streamoff>(currentPage.startOffset()));
552  updatedPageOffsets.push_back(static_cast<uint64>(stream().tellp())); // memorize offset to update checksum later
553  copyHelper.copy(backupStream, stream(), 27);
554  stream().seekp(-9, ios_base::cur);
555  writer().writeUInt32LE(pageSequenceNumber);
556  stream().seekp(5, ios_base::cur);
557  copyHelper.copy(backupStream, stream(), pageSize - 27);
558  } else {
559  // copy page unchanged
560  backupStream.seekg(static_cast<streamoff>(currentPage.startOffset()));
561  copyHelper.copy(backupStream, stream(), pageSize);
562  }
563  ++pageSequenceNumber;
564  }
565  }
566 
567  // report new size
568  fileInfo().reportSizeChanged(static_cast<uint64>(stream().tellp()));
569 
570  // "save as path" is now the regular path
571  if (!fileInfo().saveFilePath().empty()) {
572  fileInfo().reportPathChanged(fileInfo().saveFilePath());
573  fileInfo().setSaveFilePath(string());
574  }
575 
576  // close backups stream; reopen new file as readable stream
577  backupStream.close();
578  fileInfo().close();
579  fileInfo().stream().open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
580 
581  // update checksums of modified pages
582  for (auto offset : updatedPageOffsets) {
584  }
585 
586  // clear iterator
587  m_iterator.clear(fileInfo().stream(), startOffset(), fileInfo().size());
588 
589  } catch (...) {
590  m_iterator.setStream(fileInfo().stream());
591  BackupHelper::handleFailureAfterFileModified(fileInfo(), backupPath, fileInfo().stream(), backupStream, diag, context);
592  }
593 }
594 
595 } // namespace TagParser
void nextPage()
Increases the current position by one page.
Definition: oggiterator.cpp:63
byte headerTypeFlag() const
Returns the header type flag.
Definition: oggpage.h:103
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition: exceptions.h:32
Specialization of TagParser::VorbisComment for Vorbis comments inside an OGG stream.
Definition: oggcontainer.h:67
The Tag class is used to store, read and write tag information.
Definition: tag.h:95
void reset()
Resets the iterator to point at the first segment of the first page (matching the filter if set)...
Definition: oggiterator.cpp:46
The OggParameter struct holds the OGG parameter for a VorbisComment.
Definition: oggcontainer.h:27
uint32 dataSize() const
Returns the data size in byte.
Definition: oggpage.h:228
uint32 totalSize() const
Returns the total size of the page in byte.
Definition: oggpage.h:236
GeneralMediaFormat
The GeneralMediaFormat enum specifies the general format of media data (PCM, MPEG-4, PNG, ...).
Definition: mediaformat.h:28
const OggPage & currentPage() const
Returns the current OGG page.
Definition: oggiterator.h:131
void removeFilter()
Removes a previously set filter.
Definition: oggiterator.h:252
STL namespace.
std::iostream & stream()
Returns the related stream.
OggVorbisComment * createTag(const TagTarget &target) override
Creates a new tag.
GeneralMediaFormat general
Definition: mediaformat.h:256
const std::vector< uint32 > & segmentSizes() const
Returns the sizes of the segments of the page in byte.
Definition: oggpage.h:210
uint64 currentSegmentOffset() const
Returns the start offset of the current segment in the input stream if the iterator is valid; otherwi...
Definition: oggiterator.h:202
uint32 sequenceNumber() const
Returns the page sequence number.
Definition: oggpage.h:175
void ignore(std::size_t count=1)
Advances the position of the next character to be read from the OGG stream by count bytes...
TAG_PARSER_EXPORT const char * comment()
void parseTracks(Diagnostics &diag)
Parses the tracks of the file if not parsed yet.
uint64 startOffset() const
Returns the start offset of the page.
Definition: oggpage.h:84
std::size_t startPage() const
Definition: oggstream.h:34
std::vector< uint32 >::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:193
void setDataSize(uint32 dataSize)
Sets the length in bytes of the meta data (excluding the size of the header itself).
Definition: flacmetadata.h:105
The MediaFileInfo class allows to read and write tag information providing a container/tag format ind...
Definition: mediafileinfo.h:44
OggParameter & oggParams()
Returns the OGG parameter for the comment.
Definition: oggcontainer.h:110
Implementation of TagParser::AbstractTrack for OGG streams.
Definition: oggstream.h:13
const TagTarget & target() const
Returns the target of tag.
Definition: tag.h:232
std::size_t lastSegmentIndex
Definition: oggcontainer.h:34
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:163
Implementation of TagParser::Tag for Vorbis comments.
Definition: vorbiscomment.h:25
ChronoUtilities::TimeSpan m_duration
void removeAllTags() override
Actually just flags all tags as removed and clears all assigned fields.
uint32 checksum() const
Returns the page checksum.
Definition: oggpage.h:190
const std::vector< OggPage > & pages() const
Returns a vector of containing the OGG pages that have been fetched yet.
Definition: oggiterator.h:122
bool resyncAt(uint64 offset)
Fetches the next page at the specified offset.
Contains utility classes helping to read and write streams.
void makeHeader(std::ostream &outputStream)
Writes the header to the specified outputStream.
void setStream(std::istream &stream)
Sets the stream.
Definition: oggiterator.h:98
uint32 streamSerialNumber() const
Returns the stream serial number.
Definition: oggpage.h:156
std::size_t firstSegmentIndex
Definition: oggcontainer.h:32
void setPageIndex(std::vector< OggPage >::size_type index)
Sets the current page index.
Definition: oggiterator.h:172
OggVorbisComment * tag(std::size_t index) override
Returns the tag with the specified index.
IoUtilities::BinaryWriter & writer()
Returns the related BinaryWriter.
bool removeTag(Tag *tag) override
Actually just flags the specified tag as removed and clears all assigned fields.
The TagTarget class specifies the target of a tag.
Definition: tagtarget.h:21
MediaFormat format() const
Returns the format of the track if known; otherwise returns MediaFormat::Unknown. ...
The FlacMetaDataBlockHeader class is a FLAC "METADATA_BLOCK_HEADER" parser and maker.
Definition: flacmetadata.h:29
void reportPathChanged(const std::string &newPath)
Call this function to report that the path changed.
uint32 dataSize() const
Returns the length in bytes of the meta data (excluding the size of the header itself).
Definition: flacmetadata.h:96
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks...
void close()
A possibly opened std::fstream will be closed.
void internalParseHeader(Diagnostics &diag) override
Internally called to parse the header.
uint64 id() const
Returns the track ID if known; otherwise returns 0.
void updateStep(const std::string &step, byte stepPercentage=0)
Updates the current step and invokes the first callback specified on construction.
void internalParseTracks(Diagnostics &diag) override
Internally called to parse the tracks.
void setType(FlacMetaDataBlockType type)
Sets the block type.
Definition: flacmetadata.h:88
TAG_PARSER_EXPORT void handleFailureAfterFileModified(MediaFileInfo &mediaFileInfo, const std::string &backupPath, IoUtilities::NativeFileStream &outputStream, IoUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
void reportSizeChanged(uint64 newSize)
Call this function to report that the size changed.
const IdContainerType & tracks() const
Returns the tracks.
Definition: tagtarget.h:104
static void updateChecksum(std::iostream &stream, uint64 startOffset)
Updates the checksum of the page read from the specified stream at the specified startOffset.
Definition: oggpage.cpp:112
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:25
void setSaveFilePath(const std::string &saveFilePath)
Sets the "save file path".
std::size_t lastPageIndex
Definition: oggcontainer.h:33
std::size_t tagCount() const override
Returns the number of tags attached to the container.
void setLast(byte last)
Sets whether this is the last metadata block before the audio blocks.
Definition: flacmetadata.h:71
GeneralMediaFormat streamFormat
Definition: oggcontainer.h:36
uint64 startOffset() const
Returns the start offset in the related stream.
IoUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
Definition: basicfileinfo.h:79
The OggPage class is used to parse OGG pages.
Definition: oggpage.h:14
void setSegmentIndex(std::vector< uint32 >::size_type index)
Sets the current segment index.
Definition: oggiterator.h:184
void internalParseTags(Diagnostics &diag) override
Internally called to parse the tags.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
TAG_PARSER_EXPORT void createBackupFile(const std::string &originalPath, std::string &backupPath, IoUtilities::NativeFileStream &originalStream, IoUtilities::NativeFileStream &backupStream)
static uint32 computeChecksum(std::istream &stream, uint64 startOffset)
Computes the actual checksum of the page read from the specified stream at the specified startOffset...
Definition: oggpage.cpp:75
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:9
std::size_t firstPageIndex
Definition: oggcontainer.h:31
void parseTags(Diagnostics &diag)
Parses the tag information if not parsed yet.
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:154
void reset() override
Discards all parsing results.
void clear(std::istream &stream, uint64 startOffset, uint64 streamSize)
Sets the stream and related parameters and clears all available pages.
Definition: oggiterator.cpp:32
void internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to make the file.
The GenericContainer class helps parsing header, track, tag and chapter information of a file...