Tag Parser: ogg/oggcontainer.cpp Source File

Tag Parser 12.5.3 C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags

Loading...
Searching...
No Matches
oggcontainer.cpp
Go to the documentation of this file.
1#include "./oggcontainer.h"
2
3#include "../flac/flacmetadata.h"
4
5#include "../backuphelper.h"
6#include "../mediafileinfo.h"
7#include "../progressfeedback.h"
8#include "../tagtarget.h"
9
10#include <c++utilities/conversion/stringbuilder.h>
11#include <c++utilities/io/copy.h>
12
13#include <limits>
14#include <memory>
15
16using namespace std;
17using namespace CppUtilities;
18
19namespace TagParser {
20
25
26std::string_view OggVorbisComment::typeName() const
27{
28 switch (m_oggParams.streamFormat) {
29 case GeneralMediaFormat::Flac:
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
44
48OggContainer::OggContainer(MediaFileInfo &fileInfo, std::uint64_t startOffset)
49 : GenericContainer<MediaFileInfo, OggVorbisComment, OggStream, OggPage>(fileInfo, startOffset)
50 , m_iterator(fileInfo.stream(), startOffset, fileInfo.size())
51 , m_validateChecksums(false)
52{
53}
54
55OggContainer::~OggContainer()
56{
57}
58
59 void OggContainer::reset()
60{
61 m_iterator.reset();
62}
63
74 OggVorbisComment *OggContainer::createTag(const TagTarget &target)
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 const auto format = track->format().general;
105 if (format == GeneralMediaFormat::Vorbis || format == GeneralMediaFormat::Opus) {
106 // check whether start page has a valid value
107 if (track->startPage() < m_iterator.pages().size()) {
108 announceComment(track->startPage(), numeric_limits<size_t>::max(), false, track->format().general);
109 m_tags.back()->setTarget(target);
110 return m_tags.back().get();
111 } else {
112 // TODO: error handling?
113 }
114 }
115 // TODO: allow adding tags to FLAC tracks (not really important, because a tag should always be present)
116 }
117 }
118 return nullptr;
119}
120
121 OggVorbisComment *OggContainer::tag(std::size_t index)
122{
123 auto i = std::size_t();
124 for (const auto &tag : m_tags) {
125 if (!tag->oggParams().removed) {
126 if (index == i) {
127 return tag.get();
128 }
129 ++i;
130 }
131 }
132 return nullptr;
133}
134
135 size_t OggContainer::tagCount() const
136{
137 auto count = std::size_t();
138 for (const auto &tag : m_tags) {
139 if (!tag->oggParams().removed) {
140 ++count;
141 }
142 }
143 return count;
144}
145
155 bool OggContainer::removeTag(Tag *tag)
156{
157 for (auto &existingTag : m_tags) {
158 if (static_cast<Tag *>(existingTag.get()) == tag) {
159 existingTag->removeAllFields();
160 existingTag->oggParams().removed = true;
161 return true;
162 }
163 }
164 return false;
165}
166
177 void OggContainer::removeAllTags()
178{
179 for (auto &existingTag : m_tags) {
180 existingTag->removeAllFields();
181 existingTag->oggParams().removed = true;
182 }
183}
184
185 void OggContainer::internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress)
186{
187 CPP_UTILITIES_UNUSED(progress)
188
189 static const auto context = std::string("parsing Ogg bitstream header");
190 auto pagesSkipped = false, continueFromHere = false;
191
192 // iterate through pages using OggIterator helper class
193 try {
194 // ensure iterator is setup properly
195 for (m_iterator.removeFilter(), m_iterator.reset(); m_iterator;
196 continueFromHere ? [&] { continueFromHere = false; }() : m_iterator.nextPage()) {
197 progress.stopIfAborted();
198 const OggPage &page = m_iterator.currentPage();
199 if (m_validateChecksums && page.checksum() != OggPage::computeChecksum(stream(), page.startOffset())) {
200 diag.emplace_back(DiagLevel::Warning,
201 argsToString(
202 "The denoted checksum of the Ogg page at ", m_iterator.currentSegmentOffset(), " does not match the computed checksum."),
203 context);
204 }
205 OggStream *stream;
206 auto lastNewStreamOffset = std::uint64_t();
207 if (const auto streamIndex = m_streamsBySerialNo.find(page.streamSerialNumber()); streamIndex != m_streamsBySerialNo.end()) {
208 stream = m_tracks[streamIndex->second].get();
209 } else {
210 // new stream serial number recognized -> add new stream
211 m_streamsBySerialNo[page.streamSerialNumber()] = m_tracks.size();
212 stream = m_tracks.emplace_back(make_unique<OggStream>(*this, m_iterator.currentPageIndex())).get();
213 lastNewStreamOffset = page.startOffset();
214 }
215 if (!pagesSkipped) {
216 stream->m_size += page.dataSize();
217 }
218 if (stream->m_currentSequenceNumber != page.sequenceNumber()) {
219 if (stream->m_currentSequenceNumber) {
220 diag.emplace_back(DiagLevel::Warning,
221 argsToString("Page of stream ", page.streamSerialNumber(), " missing; page sequence number ", stream->m_currentSequenceNumber,
222 " omitted at ", page.startOffset(), ", found ", page.sequenceNumber(), " instead."),
223 context);
224 }
225 stream->m_currentSequenceNumber = page.sequenceNumber() + 1;
226 } else {
227 ++stream->m_currentSequenceNumber;
228 }
229
230 // 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
231 if (!fileInfo().isForcingFullParse() && (fileInfo().size() - page.startOffset()) > (100 * 0x100000)
232 && (page.startOffset() - lastNewStreamOffset) > (20 * 0x100000)) {
233 if (m_iterator.resyncAt(fileInfo().size() - (20 * 0x100000))) {
234 const OggPage &resyncedPage = m_iterator.currentPage();
235 // prevent warning about missing pages by resetting the sequence number of all streams and invalidate the stream size
236 for (auto &trackStream : m_tracks) {
237 trackStream->m_currentSequenceNumber = 0;
238 trackStream->m_size = 0;
239 }
240 pagesSkipped = continueFromHere = true;
241 diag.emplace_back(DiagLevel::Information,
242 argsToString("Pages in the middle of the file (", dataSizeToString(resyncedPage.startOffset() - page.startOffset()),
243 ") have been skipped to improve parsing speed. Hence track sizes can not be computed. Maybe not even all tracks could be "
244 "detected. Force a full parse to prevent this."),
245 context);
246 } else {
247 // abort if skipping pages didn't work
248 diag.emplace_back(DiagLevel::Critical,
249 "Unable to re-sync after skipping Ogg pages in the middle of the file. Try forcing a full parse.", context);
250 return;
251 }
252 }
253 }
254 } catch (const TruncatedDataException &) {
255 // thrown when page exceeds max size
256 diag.emplace_back(DiagLevel::Critical, "The Ogg file is truncated.", context);
257 } catch (const InvalidDataException &) {
258 // thrown when first 4 byte do not match capture pattern
259 const auto expectedOffset = m_iterator.currentSegmentOffset();
260 diag.emplace_back(DiagLevel::Critical, argsToString("Capture pattern \"OggS\" at ", expectedOffset, " expected."), context);
261 if (m_iterator.resyncAt(expectedOffset)) {
262 diag.emplace_back(DiagLevel::Warning,
263 argsToString("Found next capture pattern \"OggS\" at ", m_iterator.currentPageOffset(), ". Skipped ",
264 m_iterator.currentPageOffset() - expectedOffset, " invalid bytes."),
265 context);
266 continueFromHere = true;
267 } else {
268 diag.emplace_back(DiagLevel::Critical,
269 argsToString(
270 "Aborting after not being able to find any \"OggS\" capture patterns within 65307 bytes (from offset ", expectedOffset, ")."),
271 context);
272 }
273 }
274}
275
276 void OggContainer::internalParseTags(Diagnostics &diag, AbortableProgressFeedback &progress)
277{
278 // tracks needs to be parsed before because tags are stored at stream level
279 parseTracks(diag, progress);
280 auto flags = VorbisCommentFlags::None;
281 if (fileInfo().fileHandlingFlags() & MediaFileHandlingFlags::ConvertTotalFields) {
282 flags += VorbisCommentFlags::ConvertTotalFields;
283 }
284 for (auto &comment : m_tags) {
285 OggParameter &params = comment->oggParams();
286 m_iterator.setPageIndex(params.firstPageIndex);
287 m_iterator.setSegmentIndex(params.firstSegmentIndex);
288 m_iterator.setFilter(m_iterator.currentPage().streamSerialNumber());
289 const auto startOffset = m_iterator.startOffset();
290 const auto context = argsToString("parsing tag in Ogg page at ", startOffset);
291 auto padding = std::uint64_t();
292 switch (params.streamFormat) {
293 case GeneralMediaFormat::Vorbis:
294 comment->parse(m_iterator, flags, padding, diag);
295 break;
296 case GeneralMediaFormat::Opus:
297 // skip header (has already been detected by OggStream)
298 m_iterator.ignore(8);
299 comment->parse(m_iterator, flags | VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte, padding, diag);
300 break;
301 case GeneralMediaFormat::Flac:
302 m_iterator.ignore(4);
303 comment->parse(m_iterator, flags | VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte, padding, diag);
304 break;
305 default:
306 diag.emplace_back(DiagLevel::Critical, "Stream format not supported.", context);
307 }
308 params.lastPageIndex = m_iterator.currentPageIndex();
309 params.lastSegmentIndex = m_iterator.currentSegmentIndex();
310 fileInfo().reportPaddingSizeChanged(fileInfo().paddingSize() + padding);
311
312 // do a few sanity checks on the continued-flag and absolute granule position as some Ogg demuxers are picky about them
313 static constexpr auto noPacketsFinishOnPage = std::numeric_limits<std::uint64_t>::max();
314 if (params.firstPageIndex != params.lastPageIndex) {
315 const auto pageCount = params.lastPageIndex - params.firstPageIndex;
316 for (auto i = params.firstPageIndex; i < params.lastPageIndex; ++i) {
317 if (const auto &page = m_iterator.pages()[i]; page.absoluteGranulePosition() != noPacketsFinishOnPage) {
318 diag.emplace_back(DiagLevel::Warning,
319 argsToString("Tag spans over ", pageCount, " pages but absolute granule position of unfinished page at ", page.startOffset(),
320 " is not set to \"-1\" (it is ", page.absoluteGranulePosition(), ")."),
321 context);
322 }
323 }
324 for (auto i = params.firstPageIndex + 1; i <= params.lastPageIndex; ++i) {
325 if (const auto &page = m_iterator.pages()[i]; !page.isContinued()) {
326 diag.emplace_back(DiagLevel::Warning,
327 argsToString("The tag is continued in Ogg page at ", page.startOffset(), " but this page is not marked as continued packet."),
328 context);
329 }
330 }
331 }
332 if (const auto &page = m_iterator.pages()[params.lastPageIndex]; page.absoluteGranulePosition() == noPacketsFinishOnPage) {
333 diag.emplace_back(
334 DiagLevel::Warning, argsToString("Absolute granule position of final page at ", page.startOffset(), " is set to \"-1\"."), context);
335 }
336 }
337}
338
350void OggContainer::announceComment(std::size_t pageIndex, std::size_t segmentIndex, bool lastMetaDataBlock, GeneralMediaFormat mediaFormat)
351{
352 auto &tag = m_tags.emplace_back(make_unique<OggVorbisComment>());
353 tag->oggParams().set(pageIndex, segmentIndex, lastMetaDataBlock, mediaFormat);
354 tag->target().tracks().emplace_back(m_iterator.pages()[pageIndex].streamSerialNumber());
355}
356
357 void OggContainer::internalParseTracks(Diagnostics &diag, AbortableProgressFeedback &progress)
358{
359 static const string context("parsing Ogg stream");
360 for (auto &stream : m_tracks) {
361 if (progress.isAborted()) {
362 throw OperationAbortedException();
363 }
364 try { // try to parse header
365 stream->parseHeader(diag, progress);
366 if (stream->duration() > m_duration) {
367 m_duration = stream->duration();
368 }
369 } catch (const Failure &) {
370 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse stream at ", stream->startOffset(), '.'), context);
371 }
372 }
373}
374
379void OggContainer::makeVorbisCommentSegment(stringstream &buffer, CopyHelper<65307> &copyHelper, vector<std::uint32_t> &newSegmentSizes,
380 VorbisComment *comment, OggParameter *params, Diagnostics &diag)
381{
382 const auto offset = buffer.tellp();
383 switch (params->streamFormat) {
384 case GeneralMediaFormat::Vorbis:
385 comment->make(buffer, VorbisCommentFlags::None, diag);
386 break;
387 case GeneralMediaFormat::Opus:
388 BE::getBytes(static_cast<std::uint64_t>(0x4F70757354616773u), copyHelper.buffer());
389 buffer.write(copyHelper.buffer(), 8);
390 comment->make(buffer, VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte, diag);
391 break;
392 case GeneralMediaFormat::Flac: {
393 // Vorbis comment must be wrapped in "METADATA_BLOCK_HEADER"
394 FlacMetaDataBlockHeader header;
395 header.setLast(params->lastMetaDataBlock);
396 header.setType(FlacMetaDataBlockType::VorbisComment);
397
398 // write the header later, when the size is known
399 buffer.write(copyHelper.buffer(), 4);
400
401 comment->make(buffer, VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte, diag);
402
403 // finally make the header
404 header.setDataSize(static_cast<std::uint32_t>(buffer.tellp() - offset - 4));
405 if (header.dataSize() > 0xFFFFFF) {
406 diag.emplace_back(
407 DiagLevel::Critical, "Size of Vorbis comment exceeds size limit for FLAC \"METADATA_BLOCK_HEADER\".", "making Vorbis Comment");
408 }
409 buffer.seekp(offset);
410 header.makeHeader(buffer);
411 buffer.seekp(header.dataSize(), ios_base::cur);
412 break;
413 }
414 default:;
415 }
416 MediaFileInfo::writePadding(buffer, fileInfo().preferredPadding());
417
418 newSegmentSizes.push_back(static_cast<std::uint32_t>(buffer.tellp() - offset));
419}
420
421 void OggContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress)
422{
423 const auto context = std::string("making Ogg file");
424 progress.nextStepOrStop("Prepare for rewriting Ogg file ...");
425 parseTags(diag, progress); // tags need to be parsed before the file can be rewritten
426 auto originalPath = fileInfo().path(), backupPath = std::string();
427 auto backupStream = NativeFileStream();
428
429 if (fileInfo().saveFilePath().empty()) {
430 // move current file to temp dir and reopen it as backupStream, recreate original file
431 try {
432 BackupHelper::createBackupFileCanonical(fileInfo().backupDirectory(), originalPath, backupPath, fileInfo().stream(), backupStream);
433 // recreate original file, define buffer variables
434 fileInfo().stream().open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
435 } catch (const std::ios_base::failure &failure) {
436 diag.emplace_back(
437 DiagLevel::Critical, argsToString("Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
438 throw;
439 }
440 } else {
441 // open the current file as backupStream and create a new outputStream at the specified "save file path"
442 try {
443 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
444 backupStream.open(BasicFileInfo::pathForOpen(fileInfo().path()).data(), ios_base::in | ios_base::binary);
445 fileInfo().close();
446 fileInfo().stream().open(
447 BasicFileInfo::pathForOpen(fileInfo().saveFilePath()).data(), ios_base::out | ios_base::binary | ios_base::trunc);
448 } catch (const std::ios_base::failure &failure) {
449 diag.emplace_back(DiagLevel::Critical, argsToString("Opening streams to write output file failed: ", failure.what()), context);
450 throw;
451 }
452 }
453
454 const auto totalFileSize = fileInfo().size();
455 try {
456 progress.nextStepOrStop("Writing Ogg pages ...");
457
458 // prepare iterating comments
459 OggVorbisComment *currentComment;
460 OggParameter *currentParams;
461 auto tagIterator = m_tags.cbegin(), tagEnd = m_tags.cend();
462 if (tagIterator != tagEnd) {
463 currentParams = &(currentComment = tagIterator->get())->oggParams();
464 } else {
465 currentComment = nullptr;
466 currentParams = nullptr;
467 }
468
469 // define misc variables
470 const OggPage *lastPage = nullptr;
471 static constexpr auto oggPageHeaderSize = 27u;
472 auto lastPageNewOffset = std::uint64_t();
473 auto copyHelper = CopyHelper<65307>();
474 auto updatedPageOffsets = std::vector<std::uint64_t>();
475 auto nextPageOffset = std::uint64_t();
476 auto pageSequenceNumberBySerialNo = std::unordered_map<std::uint32_t, std::uint32_t>();
477
478 // iterate through all pages of the original file
479 auto updateTick = 0u;
480 for (m_iterator.setStream(backupStream), m_iterator.removeFilter(), m_iterator.reset(); m_iterator; m_iterator.nextPage(), ++updateTick) {
481 const OggPage &currentPage = m_iterator.currentPage();
482 if (updateTick % 10) {
483 progress.updateStepPercentage(static_cast<std::uint8_t>(currentPage.startOffset() * 100ul / totalFileSize));
484 progress.stopIfAborted();
485 }
486
487 // check for gaps
488 // note: This is not just to print diag messages but also for taking into account that the parser might skip pages
489 // unless a full parse has been enforced.
490 if (lastPage && currentPage.startOffset() != nextPageOffset) {
491 m_iterator.pages().resize(m_iterator.currentPageIndex() - 1); // drop all further pages after the last consecutively parsed one
492 if (m_iterator.resyncAt(nextPageOffset)) {
493 // try again at the page we've just found
494 const auto actuallyNextPageOffset = m_iterator.currentPageOffset();
495 if (actuallyNextPageOffset != nextPageOffset) {
496 diag.emplace_back(DiagLevel::Critical,
497 argsToString("Expected Ogg page at offset ", nextPageOffset, " but found the next Ogg page only at offset ",
498 actuallyNextPageOffset, ". Skipped ", (actuallyNextPageOffset - nextPageOffset), " invalid bytes."),
499 context);
500 nextPageOffset = actuallyNextPageOffset;
501 }
502 m_iterator.previousPage();
503 continue;
504 } else {
505 diag.emplace_back(DiagLevel::Critical,
506 argsToString(
507 "Expected Ogg page at offset ", nextPageOffset, " but could not find any further pages. Skipped the rest of the file."),
508 context);
509 break;
510 }
511 }
512 const auto pageSize = currentPage.totalSize();
513 auto &pageSequenceNumber = pageSequenceNumberBySerialNo[currentPage.streamSerialNumber()];
514 lastPage = &currentPage;
515 lastPageNewOffset = static_cast<std::uint64_t>(stream().tellp());
516 nextPageOffset = currentPage.startOffset() + pageSize;
517
518 // check whether the Vorbis Comment is present in this Ogg page
519 if (currentComment && m_iterator.currentPageIndex() >= currentParams->firstPageIndex
520 && m_iterator.currentPageIndex() <= currentParams->lastPageIndex && !currentPage.segmentSizes().empty()) {
521 // page needs to be rewritten (not just copied)
522 // -> write segments to a buffer first
523 auto buffer = std::stringstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary);
524 auto newSegmentSizes = std::vector<std::uint32_t>();
525 newSegmentSizes.reserve(currentPage.segmentSizes().size());
526 auto segmentOffset = m_iterator.currentSegmentOffset();
527 auto segmentIndex = std::vector<std::uint32_t>::size_type();
528 for (const auto segmentSize : currentPage.segmentSizes()) {
529 if (!segmentSize) {
530 ++segmentIndex;
531 continue;
532 }
533 // check whether this segment contains the Vorbis Comment
534 if (currentParams
535 && (m_iterator.currentPageIndex() >= currentParams->firstPageIndex && segmentIndex >= currentParams->firstSegmentIndex)
536 && (m_iterator.currentPageIndex() <= currentParams->lastPageIndex && segmentIndex <= currentParams->lastSegmentIndex)) {
537 // prevent making the comment twice if it spreads over multiple pages/segments
538 if (!currentParams->removed
539 && ((m_iterator.currentPageIndex() == currentParams->firstPageIndex
540 && m_iterator.currentSegmentIndex() == currentParams->firstSegmentIndex))) {
541 makeVorbisCommentSegment(buffer, copyHelper, newSegmentSizes, currentComment, currentParams, diag);
542 }
543
544 // proceed with next comment?
545 if (m_iterator.currentPageIndex() > currentParams->lastPageIndex
546 || (m_iterator.currentPageIndex() == currentParams->lastPageIndex && segmentIndex > currentParams->lastSegmentIndex)) {
547 if (++tagIterator != tagEnd) {
548 currentParams = &(currentComment = tagIterator->get())->oggParams();
549 } else {
550 currentComment = nullptr;
551 currentParams = nullptr;
552 }
553 }
554 } else {
555 // copy other segments unchanged
556 backupStream.seekg(static_cast<std::streamoff>(segmentOffset));
557 copyHelper.copy(backupStream, buffer, segmentSize);
558 newSegmentSizes.push_back(segmentSize);
559
560 // check whether there is a new comment to be inserted into the current page
561 if (currentParams && m_iterator.currentPageIndex() == currentParams->lastPageIndex
562 && currentParams->firstSegmentIndex == numeric_limits<size_t>::max()) {
563 if (!currentParams->removed) {
564 makeVorbisCommentSegment(buffer, copyHelper, newSegmentSizes, currentComment, currentParams, diag);
565 }
566 // proceed with next comment
567 if (++tagIterator != tagEnd) {
568 currentParams = &(currentComment = tagIterator->get())->oggParams();
569 } else {
570 currentComment = nullptr;
571 currentParams = nullptr;
572 }
573 }
574 }
575 segmentOffset += segmentSize;
576 ++segmentIndex;
577 }
578
579 // write buffered data to actual stream
580 if (auto newSegmentSizesIterator = newSegmentSizes.cbegin(), newSegmentSizesEnd = newSegmentSizes.cend();
581 newSegmentSizesIterator != newSegmentSizesEnd) {
582 auto bytesLeft = *newSegmentSizesIterator;
583 auto continuePreviousSegment = false, needsZeroLacingValue = false;
584 // write pages until all data in the buffer is written
585 while (newSegmentSizesIterator != newSegmentSizesEnd) {
586 // memorize offset to update checksum later
587 updatedPageOffsets.push_back(static_cast<std::uint64_t>(stream().tellp()));
588 // copy page header from original file (except for the segment table)
589 backupStream.seekg(static_cast<streamoff>(currentPage.startOffset()));
590 copyHelper.copy(backupStream, stream(), oggPageHeaderSize);
591 // use flags of original page as base and adjust "continued packet"-flag as needed
592 auto flags = (currentPage.headerTypeFlag() & 0xFE) | (continuePreviousSegment ? 0x01 : 0x00);
593 continuePreviousSegment = true;
594 // ensure "first page of logical bitstream"-flag is cleared for additional pages we need to insert
595 // ensure "last page of logical bitstream"-flag is cleared for the first page
596 flags = flags & (newSegmentSizesIterator != newSegmentSizes.cbegin() ? 0xFD : 0xF);
597 // override flags copied from original file
598 stream().seekp(-22, ios_base::cur);
599 stream().put(static_cast<char>(flags));
600 // update absolute granule position later (8 byte) and keep stream serial number (4 byte)
601 stream().seekp(12, ios_base::cur);
602 // adjust page sequence number
603 writer().writeUInt32LE(pageSequenceNumber);
604 // skip checksum (4 bytes) and number of page segments (1 byte); those are update later
605 stream().seekp(5, ios_base::cur);
606 // write segment sizes as long as there are segment sizes to be written and
607 // the max number of segment sizes (255) is not exceeded
608 auto segmentSizesWritten = std::int16_t(); // in the current page header only
609 auto currentSize = std::uint32_t();
610 while ((bytesLeft || needsZeroLacingValue) && segmentSizesWritten < 0xFF) {
611 while (bytesLeft > 0xFF && segmentSizesWritten < 0xFF) {
612 stream().put(static_cast<char>(0xFF));
613 currentSize += 0xFF;
614 bytesLeft -= 0xFF;
615 ++segmentSizesWritten;
616 }
617 if ((bytesLeft || needsZeroLacingValue) && segmentSizesWritten < 0xFF) {
618 // bytes left is here <= 0xFF
619 stream().put(static_cast<char>(bytesLeft));
620 currentSize += bytesLeft;
621 needsZeroLacingValue = bytesLeft == 0xFF;
622 bytesLeft = 0;
623 ++segmentSizesWritten;
624 }
625 if (!bytesLeft && !needsZeroLacingValue) {
626 // sizes for the segment have been written
627 // -> continue with next segment
628 if (++newSegmentSizesIterator != newSegmentSizesEnd) {
629 bytesLeft = *newSegmentSizesIterator;
630 continuePreviousSegment = false;
631 }
632 }
633 }
634
635 // remove continue flag if there are no bytes left in the current segment
636 if (!bytesLeft && !needsZeroLacingValue) {
637 continuePreviousSegment = false;
638 }
639
640 // set the absolute granule postion
641 if (newSegmentSizesIterator != newSegmentSizesEnd) {
642 // set absolute granule position to special value "-1" if there are still bytes to be written in the current packet
643 stream().seekp(-21 - segmentSizesWritten, ios_base::cur);
644 writer().writeInt64LE(-1);
645 stream().seekp(12, ios_base::cur);
646 } else if (currentParams->lastPageIndex != currentParams->firstPageIndex) {
647 // ensure the written absolute granule position matches the one from the last page of the existing file
648 backupStream.seekg(static_cast<streamoff>(m_iterator.pages()[currentParams->lastPageIndex].startOffset() + 6));
649 stream().seekp(-21 - segmentSizesWritten, ios_base::cur);
650 copyHelper.copy(backupStream, stream(), 8);
651 stream().seekp(12, ios_base::cur);
652 } else {
653 // leave the absolute granule position unchanged
654 stream().seekp(-1 - segmentSizesWritten, ios_base::cur);
655 }
656
657 // page is full or all segment data has been covered
658 // -> write segment table size (segmentSizesWritten) and segment data
659 // -> seek back and write updated page segment number
660 stream().put(static_cast<char>(segmentSizesWritten));
661 stream().seekp(segmentSizesWritten, ios_base::cur);
662 // -> write actual page data
663 copyHelper.copy(buffer, stream(), currentSize);
664
665 ++pageSequenceNumber;
666 }
667 }
668
669 } else {
670 if (pageSequenceNumber != m_iterator.currentPageIndex()) {
671 // just update page sequence number
672 backupStream.seekg(static_cast<streamoff>(currentPage.startOffset()));
673 updatedPageOffsets.push_back(static_cast<std::uint64_t>(stream().tellp())); // memorize offset to update checksum later
674 copyHelper.copy(backupStream, stream(), oggPageHeaderSize);
675 stream().seekp(-9, ios_base::cur);
676 writer().writeUInt32LE(pageSequenceNumber);
677 stream().seekp(5, ios_base::cur);
678 copyHelper.copy(backupStream, stream(), pageSize - oggPageHeaderSize);
679 } else {
680 // copy page unchanged
681 backupStream.seekg(static_cast<streamoff>(currentPage.startOffset()));
682 copyHelper.copy(backupStream, stream(), pageSize);
683 }
684 ++pageSequenceNumber;
685 }
686 }
687
688 // report new size
689 fileInfo().reportSizeChanged(static_cast<std::uint64_t>(stream().tellp()));
690 progress.updateStepPercentage(100);
691
692 // "save as path" is now the regular path
693 if (!fileInfo().saveFilePath().empty()) {
694 fileInfo().reportPathChanged(fileInfo().saveFilePath());
695 fileInfo().setSaveFilePath(string());
696 }
697
698 // close backups stream; reopen new file as readable stream
699 auto &stream = fileInfo().stream();
700 backupStream.close();
701 fileInfo().close();
702 stream.open(BasicFileInfo::pathForOpen(fileInfo().path()).data(), ios_base::in | ios_base::out | ios_base::binary);
703
704 // ensure the "last page of logical bitstream"-flag is set on the last Ogg page (in case the last page was written/modified by us)
705 if (lastPage && lastPageNewOffset) {
706 const auto offset = static_cast<std::streamoff>(lastPageNewOffset + 5ul);
707 stream.seekg(offset);
708 if (const auto flag = stream.get(); !(flag & 0x04)) {
709 updatedPageOffsets.emplace_back(lastPageNewOffset);
710 stream.seekp(offset);
711 stream.put(static_cast<char>(flag | 0x04));
712 }
713 }
714
715 // update checksums of modified pages
716 progress.nextStepOrStop("Updating checksums ...");
717 updateTick = 0u;
718 for (auto offset : updatedPageOffsets) {
719 if (updateTick++ % 10) {
720 progress.updateStepPercentage(static_cast<std::uint8_t>(offset * 100ul / fileInfo().size()));
721 progress.stopIfAborted();
722 }
723 OggPage::updateChecksum(stream, offset);
724 }
725
726 // prevent deferring final write operations (to catch and handle possible errors here)
727 stream.flush();
728 progress.updateStepPercentage(100);
729
730 // clear iterator
731 m_iterator.clear(stream, startOffset(), fileInfo().size());
732
733 } catch (...) {
734 m_iterator.setStream(fileInfo().stream());
735 BackupHelper::handleFailureAfterFileModifiedCanonical(fileInfo(), originalPath, backupPath, fileInfo().stream(), backupStream, diag, context);
736 }
737}
738
739} // namespace TagParser
backuphelper.h
TagParser::AbortableProgressFeedback
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks.
Definition progressfeedback.h:186
TagParser::AbortableProgressFeedback::isAborted
bool isAborted() const
Returns whether the operation has been aborted via tryToAbort().
Definition progressfeedback.h:236
TagParser::AbortableProgressFeedback::stopIfAborted
void stopIfAborted() const
Throws an OperationAbortedException if aborted.
Definition progressfeedback.h:255
TagParser::AbortableProgressFeedback::nextStepOrStop
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.
Definition progressfeedback.h:266
TagParser::AbstractContainer::stream
std::iostream & stream()
Returns the related stream.
Definition abstractcontainer.h:136
TagParser::AbstractContainer::startOffset
std::uint64_t startOffset() const
Returns the start offset in the related stream.
Definition abstractcontainer.h:154
TagParser::AbstractContainer::parseTracks
void parseTracks(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the tracks of the file if not parsed yet.
Definition abstractcontainer.cpp:111
TagParser::AbstractContainer::writer
CppUtilities::BinaryWriter & writer()
Returns the related BinaryWriter.
Definition abstractcontainer.h:170
TagParser::AbstractContainer::parseTags
void parseTags(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the tag information if not parsed yet.
Definition abstractcontainer.cpp:87
TagParser::AbstractContainer::m_duration
CppUtilities::TimeSpan m_duration
Definition abstractcontainer.h:111
TagParser::BasicFileInfo::reportPathChanged
void reportPathChanged(std::string_view newPath)
Call this function to report that the path changed.
Definition basicfileinfo.h:133
TagParser::BasicFileInfo::path
const std::string & path() const
Returns the path of the current file.
Definition basicfileinfo.h:103
TagParser::BasicFileInfo::size
std::uint64_t size() const
Returns size of the current file in bytes.
Definition basicfileinfo.h:115
TagParser::BasicFileInfo::stream
CppUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
Definition basicfileinfo.h:85
TagParser::BasicFileInfo::close
void close()
A possibly opened std::fstream will be closed.
Definition basicfileinfo.cpp:97
TagParser::BasicFileInfo::pathForOpen
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(),...
Definition basicfileinfo.h:153
TagParser::BasicFileInfo::reportSizeChanged
void reportSizeChanged(std::uint64_t newSize)
Call this function to report that the size changed.
Definition basicfileinfo.h:124
TagParser::BasicProgressFeedback::updateStepPercentage
void updateStepPercentage(std::uint8_t stepPercentage)
Updates the current step percentage and invokes the second callback specified on construction (or the...
Definition progressfeedback.h:124
TagParser::Diagnostics
The Diagnostics class is a container for DiagMessage.
Definition diagnostics.h:156
TagParser::Failure
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition exceptions.h:11
TagParser::FlacMetaDataBlockHeader
The FlacMetaDataBlockHeader class is a FLAC "METADATA_BLOCK_HEADER" parser and maker.
Definition flacmetadata.h:28
TagParser::FlacMetaDataBlockHeader::dataSize
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
TagParser::FlacMetaDataBlockHeader::setType
void setType(FlacMetaDataBlockType type)
Sets the block type.
Definition flacmetadata.h:87
TagParser::FlacMetaDataBlockHeader::setLast
void setLast(std::uint8_t last)
Sets whether this is the last metadata block before the audio blocks.
Definition flacmetadata.h:70
TagParser::FlacMetaDataBlockHeader::setDataSize
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
TagParser::FlacMetaDataBlockHeader::makeHeader
void makeHeader(std::ostream &outputStream)
Writes the header to the specified outputStream.
Definition flacmetadata.cpp:40
TagParser::GenericContainer< MediaFileInfo, OggVorbisComment, OggStream, OggPage >::track
OggStream * track(std::size_t index) override
Definition genericcontainer.h:176
TagParser::GenericContainer< MediaFileInfo, OggVorbisComment, OggStream, OggPage >::m_tracks
std::vector< std::unique_ptr< OggStream > > m_tracks
Definition genericcontainer.h:61
TagParser::GenericContainer< MediaFileInfo, OggVorbisComment, OggStream, OggPage >::GenericContainer
GenericContainer(MediaFileInfo &fileInfo, std::uint64_t startOffset)
Definition genericcontainer.h:71
TagParser::GenericContainer< MediaFileInfo, OggVorbisComment, OggStream, OggPage >::fileInfo
MediaFileInfo & fileInfo() const
Definition genericcontainer.h:114
TagParser::GenericContainer< MediaFileInfo, OggVorbisComment, OggStream, OggPage >::m_tags
std::vector< std::unique_ptr< OggVorbisComment > > m_tags
Definition genericcontainer.h:60
TagParser::InvalidDataException
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition exceptions.h:25
TagParser::MediaFileInfo
The MediaFileInfo class allows to read and write tag information providing a container/tag format ind...
Definition mediafileinfo.h:83
TagParser::MediaFileInfo::setSaveFilePath
void setSaveFilePath(std::string_view saveFilePath)
Sets the "save file path".
Definition mediafileinfo.h:467
TagParser::MediaFileInfo::writePadding
static void writePadding(std::ostream &outputStream, uint64_t size)
Writes the specified number of zeroes to outputStream.
Definition mediafileinfo.cpp:1399
TagParser::MediaFileInfo::reportPaddingSizeChanged
void reportPaddingSizeChanged(std::uint64_t newPaddingSize)
Sets the padding size.
Definition mediafileinfo.h:411
TagParser::OggContainer::internalParseTags
void internalParseTags(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the tags.
Definition oggcontainer.cpp:276
TagParser::OggContainer::~OggContainer
~OggContainer() override
Definition oggcontainer.cpp:55
TagParser::OggContainer::tag
OggVorbisComment * tag(std::size_t index) override
Returns the tag with the specified index.
Definition oggcontainer.cpp:121
TagParser::OggContainer::tagCount
std::size_t tagCount() const override
Returns the number of tags attached to the container.
Definition oggcontainer.cpp:135
TagParser::OggContainer::internalParseTracks
void internalParseTracks(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the tracks.
Definition oggcontainer.cpp:357
TagParser::OggContainer::OggContainer
OggContainer(MediaFileInfo &fileInfo, std::uint64_t startOffset)
Constructs a new container for the specified stream at the specified startOffset.
Definition oggcontainer.cpp:48
TagParser::OggContainer::internalParseHeader
void internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the header.
Definition oggcontainer.cpp:185
TagParser::OggContainer::removeAllTags
void removeAllTags() override
Actually just flags all tags as removed and clears all assigned fields.
Definition oggcontainer.cpp:177
TagParser::OggContainer::reset
void reset() override
Discards all parsing results.
Definition oggcontainer.cpp:59
TagParser::OggContainer::createTag
OggVorbisComment * createTag(const TagTarget &target=TagTarget()) override
Creates a new tag.
Definition oggcontainer.cpp:74
TagParser::OggContainer::OggStream
friend class OggStream
Definition oggcontainer.h:135
TagParser::OggContainer::internalMakeFile
void internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to make the file.
Definition oggcontainer.cpp:421
TagParser::OggContainer::removeTag
bool removeTag(Tag *tag) override
Actually just flags the specified tag as removed and clears all assigned fields.
Definition oggcontainer.cpp:155
TagParser::OggIterator::pages
const std::vector< OggPage > & pages() const
Returns a vector of containing the Ogg pages that have been fetched yet.
Definition oggiterator.h:125
TagParser::OggPage
The OggPage class is used to parse Ogg pages.
Definition oggpage.h:13
TagParser::OggPage::sequenceNumber
std::uint32_t sequenceNumber() const
Returns the page sequence number.
Definition oggpage.h:185
TagParser::OggPage::dataSize
std::uint32_t dataSize() const
Returns the data size in byte.
Definition oggpage.h:238
TagParser::OggPage::totalSize
std::uint32_t totalSize() const
Returns the total size of the page in byte.
Definition oggpage.h:246
TagParser::OggPage::streamSerialNumber
std::uint32_t streamSerialNumber() const
Returns the stream serial number.
Definition oggpage.h:166
TagParser::OggPage::segmentSizes
const std::vector< std::uint32_t > & segmentSizes() const
Returns the sizes of the segments of the page in byte.
Definition oggpage.h:220
TagParser::OggPage::headerTypeFlag
std::uint8_t headerTypeFlag() const
Returns the header type flag.
Definition oggpage.h:105
TagParser::OggPage::startOffset
std::uint64_t startOffset() const
Returns the start offset of the page.
Definition oggpage.h:86
TagParser::OggPage::checksum
std::uint32_t checksum() const
Returns the page checksum.
Definition oggpage.h:200
TagParser::OggPage::computeChecksum
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
TagParser::OggPage::updateChecksum
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
TagParser::OggVorbisComment
Specialization of TagParser::VorbisComment for Vorbis comments inside an Ogg stream.
Definition oggcontainer.h:69
TagParser::OggVorbisComment::oggParams
OggParameter & oggParams()
Returns the Ogg parameter for the comment.
Definition oggcontainer.h:118
TagParser::OggVorbisComment::typeName
std::string_view typeName() const override
Returns the type name of the tag.
Definition oggcontainer.cpp:26
TagParser::OperationAbortedException
The exception that is thrown when an operation has been stopped and thus not successfully completed b...
Definition exceptions.h:46
TagParser::TagTarget
The TagTarget class specifies the target of a tag.
Definition tagtarget.h:20
TagParser::TagTarget::tracks
const IdContainerType & tracks() const
Returns the tracks.
Definition tagtarget.h:105
TagParser::Tag
The Tag class is used to store, read and write tag information.
Definition tag.h:179
TagParser::Tag::target
const TagTarget & target() const
Definition tag.h:257
TagParser::TruncatedDataException
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition exceptions.h:39
TagParser::VorbisComment
Implementation of TagParser::Tag for Vorbis comments.
Definition vorbiscomment.h:27
flacmetadata.h
mediafileinfo.h
CppUtilities
Definition abstractcontainer.h:16
TagParser::BackupHelper::handleFailureAfterFileModifiedCanonical
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.
Definition backuphelper.cpp:241
TagParser::BackupHelper::createBackupFileCanonical
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.
Definition backuphelper.cpp:199
TagParser
Contains all classes and functions of the TagInfo library.
Definition aaccodebook.h:10
TagParser::FlacMetaDataBlockType::VorbisComment
@ VorbisComment
Definition flacmetadata.h:16
TagParser::DiagLevel::Warning
@ Warning
Definition diagnostics.h:20
TagParser::DiagLevel::Critical
@ Critical
Definition diagnostics.h:21
TagParser::DiagLevel::Information
@ Information
Definition diagnostics.h:19
TagParser::GeneralMediaFormat
GeneralMediaFormat
The GeneralMediaFormat enum specifies the general format of media data (PCM, MPEG-4,...
Definition mediaformat.h:30
TagParser::GeneralMediaFormat::Vorbis
@ Vorbis
Definition mediaformat.h:96
TagParser::GeneralMediaFormat::Flac
@ Flac
Definition mediaformat.h:51
TagParser::GeneralMediaFormat::Theora
@ Theora
Definition mediaformat.h:88
TagParser::GeneralMediaFormat::Opus
@ Opus
Definition mediaformat.h:72
TagParser::MediaFileHandlingFlags::ConvertTotalFields
@ ConvertTotalFields
Definition mediafileinfo.h:69
TagParser::VorbisCommentFlags::ConvertTotalFields
@ ConvertTotalFields
Definition vorbiscommentfield.h:23
TagParser::VorbisCommentFlags::None
@ None
Definition vorbiscommentfield.h:19
TagParser::VorbisCommentFlags::NoSignature
@ NoSignature
Definition vorbiscommentfield.h:20
TagParser::VorbisCommentFlags::NoFramingByte
@ NoFramingByte
Definition vorbiscommentfield.h:21
oggcontainer.h
progressfeedback.h
TagParser::OggParameter
The OggParameter struct holds the Ogg parameter for a VorbisComment.
Definition oggcontainer.h:29
TagParser::OggParameter::lastMetaDataBlock
bool lastMetaDataBlock
Definition oggcontainer.h:38
TagParser::OggParameter::streamFormat
GeneralMediaFormat streamFormat
Definition oggcontainer.h:37
TagParser::OggParameter::lastPageIndex
std::size_t lastPageIndex
Definition oggcontainer.h:35
TagParser::OggParameter::lastSegmentIndex
std::size_t lastSegmentIndex
Definition oggcontainer.h:36
TagParser::OggParameter::firstSegmentIndex
std::size_t firstSegmentIndex
Definition oggcontainer.h:34
TagParser::OggParameter::set
void set(std::size_t pageIndex, std::size_t segmentIndex, bool lastMetaDataBlock, GeneralMediaFormat streamFormat=GeneralMediaFormat::Vorbis)
Sets the firstPageIndex/lastPageIndex, the firstSegmentIndex/lastSegmentIndex, whether the associated...
Definition oggcontainer.h:61
TagParser::OggParameter::firstPageIndex
std::size_t firstPageIndex
Definition oggcontainer.h:33
TagParser::OggParameter::removed
bool removed
Definition oggcontainer.h:39
tagtarget.h