Tag Parser: ogg/oggcontainer.cpp Source File

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